mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-12 17:51:53 +08:00
Compare commits
466 Commits
content/sy
...
1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c5d28b68b | ||
|
|
2f8c0c5748 | ||
|
|
5e08af99b2 | ||
|
|
2882815313 | ||
|
|
e093f98a42 | ||
|
|
d3f8e0517b | ||
|
|
efc874163b | ||
|
|
3e8abbed13 | ||
|
|
244d336d8e | ||
|
|
9d24b98f67 | ||
|
|
007bd7feb0 | ||
|
|
7619945028 | ||
|
|
3265f9729d | ||
|
|
4591ad2336 | ||
|
|
163e03f578 | ||
|
|
2215174c20 | ||
|
|
aa52e08ac4 | ||
|
|
96acb6c93e | ||
|
|
69ebd50a90 | ||
|
|
e2eaf7d19c | ||
|
|
da7ba5bf4c | ||
|
|
0d17cf145c | ||
|
|
8a7f7a4a83 | ||
|
|
7d3255576b | ||
|
|
97529cbf54 | ||
|
|
52af178a19 | ||
|
|
f0425fd964 | ||
|
|
570d6a04b1 | ||
|
|
1e677183aa | ||
|
|
14a29b4634 | ||
|
|
bc66a805e3 | ||
|
|
f6c10d7344 | ||
|
|
4e96943374 | ||
|
|
1235459d7a | ||
|
|
0d35fe0364 | ||
|
|
76d6fab581 | ||
|
|
da39147539 | ||
|
|
9e230a01a2 | ||
|
|
4c3452926a | ||
|
|
b36c5b3c26 | ||
|
|
95fe79a0f1 | ||
|
|
850a9ffc9d | ||
|
|
2b8d18d880 | ||
|
|
9b551a69a7 | ||
|
|
cdb9201b2f | ||
|
|
ab15d91614 | ||
|
|
9d2fdfa7cf | ||
|
|
bcad685e27 | ||
|
|
74ae339fe1 | ||
|
|
5811fd8832 | ||
|
|
6c710a92c1 | ||
|
|
51f068085d | ||
|
|
1795bc1495 | ||
|
|
d4ef930187 | ||
|
|
54fae335c2 | ||
|
|
0f886e9def | ||
|
|
3f299cdd8b | ||
|
|
7fccd6b399 | ||
|
|
f75512e96a | ||
|
|
32ff9a700b | ||
|
|
6976202171 | ||
|
|
cf1cca7cb3 | ||
|
|
2236c3f93c | ||
|
|
4f067504a2 | ||
|
|
42747f4f97 | ||
|
|
1d952f75f8 | ||
|
|
9e61ef5dd1 | ||
|
|
c7770cc64c | ||
|
|
af7e25dc92 | ||
|
|
cf3365e778 | ||
|
|
bd69872059 | ||
|
|
746ee3d548 | ||
|
|
73c55a0eaa | ||
|
|
299d0f3ada | ||
|
|
98097f939a | ||
|
|
f4904da3f8 | ||
|
|
465c00b4d5 | ||
|
|
69c54e5dfe | ||
|
|
6f4898c216 | ||
|
|
b8cc07c29e | ||
|
|
eae0ad3ecb | ||
|
|
56bf52e641 | ||
|
|
689f24e0f1 | ||
|
|
63d66b3f4e | ||
|
|
4930c00f78 | ||
|
|
5745fc56bf | ||
|
|
55d5ced587 | ||
|
|
018be76895 | ||
|
|
b268106684 | ||
|
|
56e2108be2 | ||
|
|
9dfbceda7c | ||
|
|
c698265f42 | ||
|
|
752d4614b8 | ||
|
|
d73e08f8f6 | ||
|
|
cf648924cf | ||
|
|
2d15290566 | ||
|
|
06dd1934f3 | ||
|
|
316ada1259 | ||
|
|
30d2f15433 | ||
|
|
4ac1319d8d | ||
|
|
4e924981c1 | ||
|
|
fdf3fd050b | ||
|
|
79afd0a6a8 | ||
|
|
03e35ee928 | ||
|
|
eaaedb8034 | ||
|
|
84e87a501e | ||
|
|
8fca669787 | ||
|
|
3c1d41119f | ||
|
|
495fd37eae | ||
|
|
4cfeb1c372 | ||
|
|
91a47faec0 | ||
|
|
8c03aedea1 | ||
|
|
9a515f85c1 | ||
|
|
0a2468aad2 | ||
|
|
fc2eb36d58 | ||
|
|
3c5ea2131d | ||
|
|
75e1f67ee8 | ||
|
|
b40894cfdc | ||
|
|
4fb2e1f46d | ||
|
|
8eccfd22e3 | ||
|
|
d84800fcaf | ||
|
|
bb3260f4b7 | ||
|
|
9a2e1fd673 | ||
|
|
3f599fab35 | ||
|
|
cdc710123f | ||
|
|
bb43c8eba6 | ||
|
|
c01d595546 | ||
|
|
77a66fd25d | ||
|
|
a93ac86766 | ||
|
|
4044dbea91 | ||
|
|
3fc9ffe8b4 | ||
|
|
880475f6de | ||
|
|
a26945288b | ||
|
|
b97ae52a1b | ||
|
|
76ddeeedb2 | ||
|
|
00b7fe6e7f | ||
|
|
c43442f127 | ||
|
|
68c62d218d | ||
|
|
47b10a1a1a | ||
|
|
1fd135d1c1 | ||
|
|
61bdc80f5a | ||
|
|
4fbefd5ae9 | ||
|
|
835476ed31 | ||
|
|
83745ae1b4 | ||
|
|
9465cfb5c2 | ||
|
|
4edd398770 | ||
|
|
21b3b7cbdf | ||
|
|
ae6763bf83 | ||
|
|
be5a61b697 | ||
|
|
8e25dca636 | ||
|
|
b91d404f17 | ||
|
|
80f2cb8cbc | ||
|
|
2dc3d4fd24 | ||
|
|
2432ff9fd4 | ||
|
|
8f1f8846c9 | ||
|
|
7dac8665a0 | ||
|
|
f0181ff08f | ||
|
|
0ad95c2dd0 | ||
|
|
d184e93519 | ||
|
|
4ef31700a5 | ||
|
|
087f4e5c25 | ||
|
|
c5ae26458a | ||
|
|
0c6de5d89b | ||
|
|
124d113162 | ||
|
|
c88b0f3b1a | ||
|
|
06d72599d9 | ||
|
|
eb9cd6cdcc | ||
|
|
c7589b8325 | ||
|
|
4c07ac509b | ||
|
|
1240b6b1bc | ||
|
|
ad05c49570 | ||
|
|
c01a854a5a | ||
|
|
7b1dde1d62 | ||
|
|
56b0275b06 | ||
|
|
7a0d784d81 | ||
|
|
2c9eb1f9ee | ||
|
|
e4ca1c9598 | ||
|
|
2b8e06d651 | ||
|
|
56088a838c | ||
|
|
542d82c2dc | ||
|
|
980322bae0 | ||
|
|
56fbe9a685 | ||
|
|
6939240d59 | ||
|
|
4caaee3da5 | ||
|
|
e829af3e62 | ||
|
|
7ba0fa9004 | ||
|
|
74433cd0d3 | ||
|
|
dec3e992b3 | ||
|
|
7a4c27460f | ||
|
|
5553b411eb | ||
|
|
98cc968ed1 | ||
|
|
3de37468a6 | ||
|
|
3364eae0a6 | ||
|
|
a06eaec5d4 | ||
|
|
10e433f538 | ||
|
|
129deed6a9 | ||
|
|
ce35a8112f | ||
|
|
35f6070133 | ||
|
|
629f1058f2 | ||
|
|
199310df93 | ||
|
|
0d45fcbf79 | ||
|
|
47cbcde5dc | ||
|
|
5b12eb9e02 | ||
|
|
6632b46d98 | ||
|
|
25e009a63f | ||
|
|
9ae7eed1e3 | ||
|
|
8db62cb19f | ||
|
|
d1a991b18c | ||
|
|
8107e008ff | ||
|
|
944858bbb1 | ||
|
|
b864c60ea3 | ||
|
|
618b55f601 | ||
|
|
b5c65b408b | ||
|
|
21f2ef80ba | ||
|
|
ebd351e133 | ||
|
|
77dab81b92 | ||
|
|
0350da2929 | ||
|
|
59c07c9000 | ||
|
|
79ab31dec7 | ||
|
|
16983cb950 | ||
|
|
e29fe52cb1 | ||
|
|
7921acb666 | ||
|
|
b53f8c982c | ||
|
|
0b72a07147 | ||
|
|
5155a0c358 | ||
|
|
bd5663ab26 | ||
|
|
af3ccd5bb5 | ||
|
|
035eaa47e8 | ||
|
|
3541d4e717 | ||
|
|
e8dcfe97f2 | ||
|
|
8f3307e53e | ||
|
|
dcc825416d | ||
|
|
f8fcb8d600 | ||
|
|
40919dec14 | ||
|
|
f3592155bf | ||
|
|
927ee73be7 | ||
|
|
4f81d5374e | ||
|
|
e95fd69886 | ||
|
|
11d9da5afb | ||
|
|
cea8abc5ef | ||
|
|
7169d3bb8f | ||
|
|
ae9c1c4992 | ||
|
|
58e560af7d | ||
|
|
09fa166f56 | ||
|
|
6ed7d9c25f | ||
|
|
e59fc5e4e9 | ||
|
|
f5da05c3ec | ||
|
|
07b200b878 | ||
|
|
ccca782f25 | ||
|
|
77d9846d9b | ||
|
|
8da175e9d8 | ||
|
|
467634889b | ||
|
|
b46b425b41 | ||
|
|
9e23439f0c | ||
|
|
c6db625e35 | ||
|
|
672245e4e4 | ||
|
|
e4ce3475c6 | ||
|
|
d15b97db73 | ||
|
|
8f040e5e8a | ||
|
|
888800d2a0 | ||
|
|
51b2c70586 | ||
|
|
5b4cc86f61 | ||
|
|
9952ee5805 | ||
|
|
dacbf09f55 | ||
|
|
a16787ab58 | ||
|
|
7d45c8e462 | ||
|
|
796bde76c9 | ||
|
|
22d5622e1e | ||
|
|
2312fdd608 | ||
|
|
bc2ecea03b | ||
|
|
84a551f906 | ||
|
|
9fab5c7134 | ||
|
|
c61f4a845d | ||
|
|
025753b279 | ||
|
|
6b9901db28 | ||
|
|
34f0e483ec | ||
|
|
0ae9bc0e3e | ||
|
|
3f17f60daf | ||
|
|
7f2acba352 | ||
|
|
907fb9915f | ||
|
|
fd2e64ec50 | ||
|
|
3fd5b9e744 | ||
|
|
edff9156ff | ||
|
|
e1c89585e9 | ||
|
|
abaa839b26 | ||
|
|
1bc7384929 | ||
|
|
6a148295f7 | ||
|
|
ea25f2d99b | ||
|
|
08303c0623 | ||
|
|
f18f9fb5b3 | ||
|
|
dfc07e0753 | ||
|
|
64a19fdc3c | ||
|
|
1b3e8712ff | ||
|
|
f242c6e358 | ||
|
|
7e2121bed9 | ||
|
|
bb80ceb7ba | ||
|
|
25dfb28368 | ||
|
|
a1c75bb9f8 | ||
|
|
efdb628120 | ||
|
|
ac23dddeb9 | ||
|
|
b208eaa1bd | ||
|
|
8ebf97277c | ||
|
|
928d79e3fb | ||
|
|
9b95218eb8 | ||
|
|
8bcdd84f0f | ||
|
|
67a72aab11 | ||
|
|
771f3a9cb7 | ||
|
|
38b6b34437 | ||
|
|
548dfd85e7 | ||
|
|
971d23c43a | ||
|
|
3aac8de849 | ||
|
|
8d605735b2 | ||
|
|
7debdb90c1 | ||
|
|
f6f5c821b3 | ||
|
|
227e08b7c4 | ||
|
|
16651606fb | ||
|
|
0ea67f695d | ||
|
|
6c4386ed7d | ||
|
|
e65ba9365b | ||
|
|
e5843568dd | ||
|
|
7968151c44 | ||
|
|
9f0753f098 | ||
|
|
98d0aa5103 | ||
|
|
c1706e2c18 | ||
|
|
84e74096b7 | ||
|
|
3d96fdf1df | ||
|
|
a157605b2b | ||
|
|
ec83830577 | ||
|
|
6babeb3f21 | ||
|
|
0b9754c9ae | ||
|
|
c2f7754b0d | ||
|
|
4df519845f | ||
|
|
910bd371dd | ||
|
|
0aa6db6007 | ||
|
|
01be603780 | ||
|
|
55a3ce4def | ||
|
|
a21264eb5e | ||
|
|
8c216782e5 | ||
|
|
ed9823245b | ||
|
|
378e53eba4 | ||
|
|
66b68bc26f | ||
|
|
0785d28bb4 | ||
|
|
f43dda522d | ||
|
|
ba98142d5b | ||
|
|
43160d3058 | ||
|
|
d40a858c6a | ||
|
|
4024005c4a | ||
|
|
91d1fc7245 | ||
|
|
328efa6ff6 | ||
|
|
cb352aba68 | ||
|
|
625ca5dcf4 | ||
|
|
25d686ae5c | ||
|
|
0ab94faa95 | ||
|
|
5299a04acd | ||
|
|
f326a58bee | ||
|
|
d8d52a6e86 | ||
|
|
ba09cc4b86 | ||
|
|
5804deb8ac | ||
|
|
63b3f0199b | ||
|
|
0b0addaee4 | ||
|
|
aab6d380aa | ||
|
|
79b5c09a06 | ||
|
|
3bd4ad5874 | ||
|
|
79887dc7d5 | ||
|
|
dc8cb8e777 | ||
|
|
a8059e73c0 | ||
|
|
ee2b3e5de0 | ||
|
|
807e5ea2c1 | ||
|
|
f7b42203a4 | ||
|
|
93d4462ec8 | ||
|
|
3643a4cf1c | ||
|
|
9c382651ee | ||
|
|
e4b5b41cd7 | ||
|
|
eb96eb2ccd | ||
|
|
b18caf0ba4 | ||
|
|
9381df73a3 | ||
|
|
f45c7d89b4 | ||
|
|
1f25d174ec | ||
|
|
037798676f | ||
|
|
747e617462 | ||
|
|
f1fb4b7026 | ||
|
|
43c165d8e7 | ||
|
|
fcd75cb9fe | ||
|
|
d88ec26bb8 | ||
|
|
8418b20c59 | ||
|
|
cfbae595e5 | ||
|
|
d89b5857d5 | ||
|
|
a1fb402bca | ||
|
|
ac40601310 | ||
|
|
e75036a664 | ||
|
|
17e7e6f967 | ||
|
|
44e4bca38d | ||
|
|
cb5f6fc7a8 | ||
|
|
26bd9b036a | ||
|
|
fed688006e | ||
|
|
71a9524e8e | ||
|
|
d20292148b | ||
|
|
63458de408 | ||
|
|
00e07394ab | ||
|
|
f5a5c8a868 | ||
|
|
e194a66bb7 | ||
|
|
3aa397862b | ||
|
|
21ee836e31 | ||
|
|
d408b44d27 | ||
|
|
6994ae22b0 | ||
|
|
fb924ace65 | ||
|
|
6fcada4a5b | ||
|
|
1273051f2f | ||
|
|
e58548bd49 | ||
|
|
fa1025fb35 | ||
|
|
665dcdf242 | ||
|
|
da96caf9ae | ||
|
|
2ac11537e1 | ||
|
|
bc2904c485 | ||
|
|
02c9d57184 | ||
|
|
34261c5517 | ||
|
|
094ac8cfa0 | ||
|
|
ecfb7a539c | ||
|
|
f30f29756b | ||
|
|
3acd331c98 | ||
|
|
d6c168acb5 | ||
|
|
d714404201 | ||
|
|
e7dcba19c5 | ||
|
|
6b3ce4b062 | ||
|
|
018b039b97 | ||
|
|
c8bf790a03 | ||
|
|
c4c7225e55 | ||
|
|
e421765752 | ||
|
|
9ee6a14fd9 | ||
|
|
036f532779 | ||
|
|
64ae6c3dda | ||
|
|
5f46572569 | ||
|
|
bcd0bbee4c | ||
|
|
78e7d90c80 | ||
|
|
350511fddd | ||
|
|
b2f4f403dd | ||
|
|
62957d667d | ||
|
|
9562d9d956 | ||
|
|
a89221a32f | ||
|
|
42c197a162 | ||
|
|
4edf2fd587 | ||
|
|
3839acba26 | ||
|
|
cc9d02d859 | ||
|
|
07c7ab018d | ||
|
|
16a04a7137 | ||
|
|
40365be22b | ||
|
|
8d990b9233 | ||
|
|
863961db67 | ||
|
|
1d155a7448 | ||
|
|
a3e0a0f154 | ||
|
|
2216dd6990 | ||
|
|
02c30296d9 | ||
|
|
b54ac3368a | ||
|
|
8219aedd2d | ||
|
|
bf2744a5c0 | ||
|
|
6f87633afc | ||
|
|
c4f7b8e7ac | ||
|
|
00f358434c | ||
|
|
7dcc87fb94 | ||
|
|
39752c73b5 | ||
|
|
07162a9e4d | ||
|
|
c4f00e016f | ||
|
|
3db0687acf | ||
|
|
13d8dd364b | ||
|
|
9ac82fb8a7 | ||
|
|
f1075dc915 |
@@ -18,6 +18,6 @@ indent_size = 2
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{package.json,.travis.yml}]
|
||||
[{package.json, .travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: kamranahmedse
|
||||
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
Please do not remove anything written below.
|
||||
|
||||
Fill the details and open the issue. Any issue that
|
||||
doesn't have all of these filled in will be closed,
|
||||
if yours is closed reopen with everything filled in.
|
||||
-->
|
||||
|
||||
#### What roadmap is this issue about?
|
||||
|
||||
- [ ] Frontend Roadmap
|
||||
- [ ] Backend Roadmap
|
||||
- [ ] DevOps Roadmap
|
||||
- [ ] All Roadmaps
|
||||
|
||||
#### What is this issue about?
|
||||
|
||||
- [ ] Functionality of the website
|
||||
- [ ] Discussion for a pull request I would want to open.
|
||||
- [ ] Addition of a new item
|
||||
- [ ] Removal of some existing item
|
||||
- [ ] Changing in arrangement
|
||||
- [ ] General suggestion
|
||||
- [ ] Sharing an Idea
|
||||
- [ ] Something else
|
||||
|
||||
#### Please acknowledge the below listed
|
||||
|
||||
- [ ] This is not a duplicate issue. I have searched and there is no existing issue for this.
|
||||
- [ ] I understand that these roadmaps are highly opinionated. The purpose is to not to include everything out there in these roadmaps but to have everything that is most relevant today comparing to the other options listed.
|
||||
- [ ] I have read the [contribution docs](../contributing) before opening this issue.
|
||||
|
||||
|
||||
#### Enter the details about the issue here
|
||||
|
||||
<!-- Please enter the issue details here -->
|
||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#### What roadmap does this PR target?
|
||||
|
||||
- [ ] Code Change
|
||||
- [ ] Frontend Roadmap
|
||||
- [ ] Backend Roadmap
|
||||
- [ ] DevOps Roadmap
|
||||
- [ ] All Roadmaps
|
||||
- [ ] Guides
|
||||
|
||||
#### Please acknowledge the items listed below
|
||||
|
||||
- [ ] I have discussed this contribution and got a go-ahead in an issue before opening this pull request.
|
||||
- [ ] This is not a duplicate issue. I have searched and there is no existing issue for this.
|
||||
- [ ] I understand that these roadmaps are highly opinionated. The purpose is to not to include everything out there in these roadmaps but to have everything that is most relevant today comparing to the other options listed.
|
||||
- [ ] I have read the [contribution docs](../contributing) before opening this PR.
|
||||
|
||||
#### Enter the details about the contribution
|
||||
|
||||
<!-- Enter the details here -->
|
||||
31
.github/workflows/deploy.yml
vendored
Normal file
31
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Deployment to GitHub Pages
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
env:
|
||||
ROADMAP_GA_SECRET: ${{ secrets.GA_SECRET }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CI: true
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
npm install -g yarn
|
||||
yarn install
|
||||
- name: Generate meta and builld
|
||||
run: |
|
||||
yarn meta
|
||||
yarn build
|
||||
- name: Deploy to GitHub Pages
|
||||
run: |
|
||||
git config user.email "kamranahmed.se@gmail.com"
|
||||
git config user.name "Kamran Ahmed"
|
||||
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
||||
npm run deploy
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,7 +1,14 @@
|
||||
.next
|
||||
.idea
|
||||
_*
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
.idea
|
||||
.next
|
||||
out
|
||||
.env
|
||||
build
|
||||
node_modules
|
||||
yarn-error.log
|
||||
yarn-error.log
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
config/*.json
|
||||
!config/dev.json
|
||||
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <kamranahmed.se@gmail.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
20
LICENSE
Normal file
20
LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
The codebase in this repository is covered under BSD 4-Clause license and
|
||||
the content with CC BY-NC-ND 3.0 with additional clauses
|
||||
|
||||
Everything including text and images in this project are protected by the copyright laws.
|
||||
You are allowed to use this material for personal use but are not allowed to use it for
|
||||
any other purpose including publishing the images, the project files or the content in the
|
||||
images in any form either digital, non-digital, textual, graphical or written formats.
|
||||
You are allowed to share the links to the repository or the website roadmap.sh but not
|
||||
the content for any sort of usage that involves the content of this repository taken out
|
||||
of the repository and be shared from any other medium including but not limited to blog
|
||||
posts, articles, newsletters, you must get prior consent from the understated.
|
||||
|
||||
Copyright © 2021 Kamran Ahmed <kamranahmed.se@gmail.com>
|
||||
|
||||
Please note that I am really flexible with allowing the usage of the content in this
|
||||
repository. If you reach out to me with a brief detail of why and how you would like
|
||||
to use this content, there is a good chance that I will allow you to use it. The reason
|
||||
behind this strictness in the license is to stop the people who have been using these
|
||||
roadmaps in ill manners e.g. ripping people off with suggesting random affiliate links,
|
||||
redistributing these roadmaps just for the sake of monetizing the traffic.
|
||||
@@ -1,15 +1,16 @@
|
||||
import { AboutHeaderWrap } from './style';
|
||||
import siteConfig from "content/site";
|
||||
|
||||
const AboutHeader = () => (
|
||||
<AboutHeaderWrap>
|
||||
<div className="container container-small">
|
||||
<div className="author-info">
|
||||
<img className='author-img d-none d-sm-none d-md-block d-lg-block d-xl-block' src="/static/kamran.jpeg" />
|
||||
<img className='author-img d-none d-sm-none d-md-block d-lg-block d-xl-block' src="/kamran.jpeg" />
|
||||
<div className="author-msg">
|
||||
<h2>Hello, I'm Kamran Ahmed.</h2>
|
||||
<h1>Hello, I'm Kamran Ahmed.</h1>
|
||||
<p>I created <span className='flow-black'>roadmap.sh</span> to help developers find their path if they are confused and help them grow in their career.</p>
|
||||
<div className="author-links">
|
||||
<a href="mailto:kamran@roadmap.sh">@kamranahmedse</a>
|
||||
<a href={`https://twitter.com/${siteConfig.twitter}`} target="_blank">@kamranahmedse</a>
|
||||
<a href="mailto:kamran@roadmap.sh">kamran@roadmap.sh</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,4 +19,4 @@ const AboutHeader = () => (
|
||||
</AboutHeaderWrap>
|
||||
);
|
||||
|
||||
export default AboutHeader;
|
||||
export default AboutHeader;
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
export const AboutHeaderWrap = styled.div`
|
||||
text-align: left;
|
||||
padding: 70px 20px;
|
||||
padding: 70px 0;
|
||||
margin: 0 auto;
|
||||
|
||||
.author-info {
|
||||
@@ -11,7 +11,7 @@ export const AboutHeaderWrap = styled.div`
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
h2 {
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -46,4 +46,4 @@ export const AboutHeaderWrap = styled.div`
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
133
components/detailed-roadmap/index.js
Normal file
133
components/detailed-roadmap/index.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faTwitterSquare, faRedditSquare, faGithubSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
PageHeader,
|
||||
RoadmapMeta,
|
||||
ShareRoadmap,
|
||||
Sidebar,
|
||||
Summary,
|
||||
SummaryContainer,
|
||||
MobileNavHeader,
|
||||
SidebarButton,
|
||||
MobileSidebar,
|
||||
MobileSidebarWrap,
|
||||
DesktopSidebarWrap,
|
||||
PageTitle,
|
||||
PageDetail
|
||||
} from './style';
|
||||
import { faBars } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getFacebookShareUrl } from 'lib/url';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
import { getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import siteConfig from "content/site";
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const DetailedRoadmap = ({ roadmap }) => {
|
||||
const [menuActive, setMenuState] = useState(false);
|
||||
const {
|
||||
sidebar = {},
|
||||
page: currentPage = {},
|
||||
author = {}
|
||||
} = roadmap;
|
||||
|
||||
const roadmapPages = Object.keys(sidebar || {}).map((groupTitle, groupCounter) => {
|
||||
if (groupTitle.startsWith('_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo remove it after completing the frontend roadmap
|
||||
const isInProgress = groupCounter !== 0;
|
||||
|
||||
return (
|
||||
<div className={`links-group ${isInProgress ? 'in-progress' : ''}`} key={groupTitle}>
|
||||
<h3>
|
||||
{ groupTitle }
|
||||
{ isInProgress && <span className='badge badge-warning progress-badge'>In Progress</span> }
|
||||
</h3>
|
||||
<ul>
|
||||
{ sidebar[groupTitle].map(page => {
|
||||
const isActivePage = page.url === currentPage.url;
|
||||
// e.g. /frontend should mark `/frontend/landscape` as active
|
||||
const isSummaryPage = page.url === `${currentPage.url}/summary`;
|
||||
|
||||
return (
|
||||
<li className={classNames({ active: isActivePage || isSummaryPage })} key={page.url}>
|
||||
<a href={ page.url }>
|
||||
<span className="bullet"></span>
|
||||
{ page.title }
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const filePath = currentPage.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<PageHeader className="border-top border-bottom text-center text-md-left">
|
||||
<div className="container d-flex align-items-center flex-column flex-md-row">
|
||||
<RoadmapMeta>
|
||||
<h3>{ roadmap.title }</h3>
|
||||
<p>
|
||||
Roadmap contributed by <a href={ author.url } target="_blank">{ author.name }</a>
|
||||
{ roadmap.contributorsCount > 1 && ` and <a href="${roadmap.contributorsUrl}">${roadmap.contributorsCount} others</a>`}
|
||||
</p>
|
||||
</RoadmapMeta>
|
||||
<ShareRoadmap className="mt-2 mt-md-0">
|
||||
<ShareIcon href={ siteConfig.url.repo } target="_blank">
|
||||
<FontAwesomeIcon icon={ faGithubSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getTwitterShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
</ShareRoadmap>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<MobileNavHeader className="border-bottom d-block d-md-none">
|
||||
<div className="container">
|
||||
<SidebarButton onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
|
||||
<FontAwesomeIcon icon={ faBars } />
|
||||
{ currentPage.title }
|
||||
</SidebarButton>
|
||||
</div>
|
||||
<MobileSidebarWrap className={classNames({ visible: menuActive })}>
|
||||
<div className="container">
|
||||
<MobileSidebar>
|
||||
{ roadmapPages }
|
||||
</MobileSidebar>
|
||||
</div>
|
||||
</MobileSidebarWrap>
|
||||
</MobileNavHeader>
|
||||
|
||||
<Summary className="container">
|
||||
<DesktopSidebarWrap className="d-none d-md-block">
|
||||
<Sidebar>
|
||||
{ roadmapPages }
|
||||
</Sidebar>
|
||||
</DesktopSidebarWrap>
|
||||
<PageDetail>
|
||||
<PageTitle>{ currentPage.title }</PageTitle>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
</PageDetail>
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default DetailedRoadmap;
|
||||
199
components/detailed-roadmap/style.js
Normal file
199
components/detailed-roadmap/style.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const Summary = styled.div`
|
||||
text-align: left;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageHeader = styled.div`
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
color: #696969;
|
||||
|
||||
a {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RoadmapMeta = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ShareRoadmap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
margin-bottom: 0;
|
||||
& + a {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileNavHeader = styled.div`
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const SidebarButton = styled.button`
|
||||
background: transparent;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
-webkit-appearance: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageDetail = styled.div`
|
||||
padding: 25px 0 100px;
|
||||
`;
|
||||
|
||||
export const PageTitle = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
export const Sidebar = styled.div`
|
||||
padding-bottom: 150px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
|
||||
.links-group {
|
||||
padding: 30px 0 10px;
|
||||
width: 260px;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.links-group.in-progress {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.links-group li {
|
||||
list-style: none;
|
||||
margin: 7px 0;
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
&.active a {
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
&.active .bullet {
|
||||
background: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DesktopSidebarWrap = styled.div`
|
||||
border-left: 1px solid #efefef;
|
||||
|
||||
${Sidebar} {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebarWrap = styled.div`
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
background: white;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 100%;
|
||||
overflow-y: scroll;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 20px;
|
||||
top: calc(100% + 1px);
|
||||
transition: bottom 0.5s ease 0s;
|
||||
|
||||
&.visible {
|
||||
bottom: -50vh;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebar = styled(Sidebar)`
|
||||
border-left: 1px solid #efefef;
|
||||
margin-left: 12px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.links-group {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
`;
|
||||
@@ -1,33 +1,16 @@
|
||||
import { FaqContainer, FaqItem } from './style';
|
||||
import { FaqContainer } from './style';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const AboutPage = require(`../../content/pages/about.md`).default;
|
||||
|
||||
const FaqList = () => (
|
||||
<FaqContainer className='border-top bg-light'>
|
||||
<FaqItem>
|
||||
<div className="container container-small">
|
||||
<h4 className='font-weight-bolder'>What is roadmap.sh?</h4>
|
||||
<p>Roadmap.sh is the community curated roadmaps and paths for the budding developers. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid assumenda iure maiores nemo nihil odio perspiciatis recusandae repellendus sed vero. Adipisci consectetur esse explicabo illum natus neque perferendis quis ullam.</p>
|
||||
<p>If you really want to Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab asperiores commodi consequatur, culpa cumque dolorum, eos hic illo obcaecati odit quam quod reiciendis, rem reprehenderit saepe sed tempore tenetur vitae!</p>
|
||||
</div>
|
||||
</FaqItem>
|
||||
<FaqItem>
|
||||
<div className="container container-small">
|
||||
<h4 className="font-weight-bolder">What are the plans for roadmap.sh?</h4>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolore eaque eius esse, facere id ipsum, minima nam, nisi quos reprehenderit saepe tempora vitae voluptate. Atque deleniti dolores eos laboriosam repellat.</p>
|
||||
</div>
|
||||
</FaqItem>
|
||||
<FaqItem>
|
||||
<div className="container container-small">
|
||||
<h4 className="font-weight-bolder">What is the source of revenue?</h4>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi consequuntur, harum impedit numquam porro quod unde! Aperiam dolorum ducimus expedita fugiat, impedit nesciunt, quaerat qui recusandae repellendus tenetur ut voluptatibus!</p>
|
||||
</div>
|
||||
</FaqItem>
|
||||
<FaqItem>
|
||||
<div className="container container-small">
|
||||
<h4 className="font-weight-bolder">Can I contribute?</h4>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi consequuntur, harum impedit numquam porro quod unde! Aperiam dolorum ducimus expedita fugiat, impedit nesciunt, quaerat qui recusandae repellendus tenetur ut voluptatibus!</p>
|
||||
</div>
|
||||
</FaqItem>
|
||||
<div className="container container-small">
|
||||
<MdRenderer>
|
||||
<AboutPage />
|
||||
</MdRenderer>
|
||||
</div>
|
||||
</FaqContainer>
|
||||
);
|
||||
|
||||
export default FaqList;
|
||||
export default FaqList;
|
||||
|
||||
@@ -2,10 +2,15 @@ import styled from 'styled-components';
|
||||
|
||||
export const FaqContainer = styled.div`
|
||||
padding: 40px 0;
|
||||
`;
|
||||
|
||||
export const FaqItem = styled.div`
|
||||
padding: 20px 20px;
|
||||
|
||||
h4 {
|
||||
margin-top: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
@@ -14,4 +19,7 @@ export const FaqItem = styled.div`
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
.container {
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -9,7 +9,7 @@ const FeaturedGuides = () => (
|
||||
<p className='border-through featured-separator'>
|
||||
<span>
|
||||
Guides mostly visited by the community
|
||||
<Link href="/guides"><a className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Guides →</a></Link>
|
||||
<a href="/guides" className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Guides →</a>
|
||||
</span>
|
||||
</p>
|
||||
<div className="swim-lane row">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import roadmaps from 'data/roadmaps';
|
||||
import roadmaps from 'content/roadmaps';
|
||||
import FeaturedRoadmap from 'components/featured-roadmap';
|
||||
|
||||
const FeaturedRoadmaps = () => (
|
||||
@@ -10,9 +9,7 @@ const FeaturedRoadmaps = () => (
|
||||
<p className="border-through featured-separator">
|
||||
<span>
|
||||
Roadmaps mostly visited by the community
|
||||
<Link href='/roadmaps'>
|
||||
<a className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Roadmaps →</a>
|
||||
</Link>
|
||||
<a href='/roadmaps' className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Roadmaps →</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Link from 'next/link';
|
||||
import formatDate from 'date-fns/format'
|
||||
import formatDate from 'date-fns/format';
|
||||
|
||||
import { Author, AuthorImage, AuthorName, BlockLink, BlockMeta, BlockSubtitle, BlockTitle, PublishDate } from './style';
|
||||
import { findByUsername } from 'lib/author';
|
||||
@@ -7,22 +6,20 @@ import { findByUsername } from 'lib/author';
|
||||
const FeaturedGuide = ({ guide }) => {
|
||||
const author = findByUsername(guide.author) || {};
|
||||
return (
|
||||
<div className="col-xl-4 col-lg-6 col-md-6 col-sm-12 col-12 grid-item-container">
|
||||
<Link href={ guide.url } passHref>
|
||||
<BlockLink>
|
||||
<BlockTitle>{ guide.title }</BlockTitle>
|
||||
<BlockSubtitle>{ guide.featuredDescription || guide.description }</BlockSubtitle>
|
||||
<BlockMeta>
|
||||
<Author>
|
||||
<AuthorImage src={ author.picture } />
|
||||
<AuthorName>{ author.name }</AuthorName>
|
||||
</Author>
|
||||
<PublishDate>{ formatDate(new Date(guide.createdAt), 'MMMM d, yyyy') }</PublishDate>
|
||||
</BlockMeta>
|
||||
</BlockLink>
|
||||
</Link>
|
||||
<div className='col-xl-4 col-lg-6 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={guide.url}>
|
||||
<BlockTitle>{guide.title}</BlockTitle>
|
||||
<BlockSubtitle>{guide.featuredDescription || guide.description}</BlockSubtitle>
|
||||
<BlockMeta>
|
||||
<Author>
|
||||
<AuthorImage src={author.picture} />
|
||||
<AuthorName>{author.name}</AuthorName>
|
||||
</Author>
|
||||
<PublishDate>{formatDate(new Date(guide.createdAt), 'MMMM d, yyyy')}</PublishDate>
|
||||
</BlockMeta>
|
||||
</BlockLink>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default FeaturedGuide;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import Link from 'next/link';
|
||||
import { BlockLink, BlockSubtitle, BlockTitle } from './style';
|
||||
|
||||
const FeaturedRoadmap = ({ roadmap }) => (
|
||||
<div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container">
|
||||
<Link href={ roadmap.url } passHref>
|
||||
<BlockLink>
|
||||
<BlockTitle>{ roadmap.title }</BlockTitle>
|
||||
<BlockSubtitle>{ roadmap.featuredDescription || roadmap.description }</BlockSubtitle>
|
||||
</BlockLink>
|
||||
</Link>
|
||||
<div className='col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={roadmap.url}>
|
||||
<BlockTitle>{roadmap.title}</BlockTitle>
|
||||
<BlockSubtitle>{roadmap.featuredDescription || roadmap.description}</BlockSubtitle>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import MdxComponents from 'components/mdx-components';
|
||||
import MdRenderer from 'components/md-renderer'
|
||||
import SharePage from 'components/share-page';
|
||||
import { GuideBodyWrap } from './style';
|
||||
|
||||
const GuideBody = (props) => (
|
||||
<MDXProvider components={ MdxComponents }>
|
||||
const GuideBody = ({ guide }) => {
|
||||
const GuideContent = require(`../../content/guides/${guide.fileName}.md`).default;
|
||||
return (
|
||||
<GuideBodyWrap>
|
||||
{ props.children }
|
||||
<MdRenderer>
|
||||
<GuideContent />
|
||||
{
|
||||
guide.author && <SharePage
|
||||
title={ guide.title }
|
||||
url={ guide.url }
|
||||
twitterUsername={ guide.author.twitter }
|
||||
/>
|
||||
}
|
||||
</MdRenderer>
|
||||
</GuideBodyWrap>
|
||||
</MDXProvider>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default GuideBody;
|
||||
|
||||
@@ -6,4 +6,8 @@ export const GuideBodyWrap = styled.div`
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
min-height: 300px;
|
||||
|
||||
p:first-child, h1, h2, h3, h4, h5, h6, img, blockquote {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import NextHead from 'next/head';
|
||||
import siteConfig from 'data/site';
|
||||
import { GA_TRACKING_ID } from 'lib/gtag';
|
||||
import siteConfig from 'content/site';
|
||||
|
||||
const prepareTitle = (givenTitle) => {
|
||||
givenTitle = givenTitle || siteConfig.title;
|
||||
return `${givenTitle} - ${siteConfig.name}`;
|
||||
return givenTitle || siteConfig.title;
|
||||
};
|
||||
|
||||
const prepareDescription = (givenDescription) => {
|
||||
return givenDescription || siteConfig.description;
|
||||
};
|
||||
|
||||
// noinspection JSUnresolvedLibraryURL
|
||||
const Helmet = (props) => (
|
||||
<NextHead>
|
||||
<meta charSet='UTF-8' />
|
||||
@@ -19,16 +18,16 @@ const Helmet = (props) => (
|
||||
<meta name='description' content={ prepareDescription(props.description) } />
|
||||
|
||||
<meta name="author" content={ siteConfig.author } />
|
||||
<meta name="keywords" content={ siteConfig.keywords.join(',') } />
|
||||
<meta name="keywords" content={ props.keywords ? props.keywords.join(',') : siteConfig.keywords.join(',') } />
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0" />
|
||||
<link rel="canonical" href={ siteConfig.url } />
|
||||
{ props.canonical && <link rel="canonical" href={ props.canonical } /> }
|
||||
<meta httpEquiv="Content-Language" content="en" />
|
||||
|
||||
<meta property="og:title" content={ prepareTitle(props.title) } />
|
||||
<meta property="og:description" content={ prepareDescription(props.description) } />
|
||||
<meta property="og:image" content={ siteConfig.logo } />
|
||||
<meta property="og:url" content={ siteConfig.url } />
|
||||
<meta property="og:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta property="og:url" content={ siteConfig.url.web } />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="article:publisher" content={ `https://facebook.com/${siteConfig.facebook}` } />
|
||||
<meta property="og:site_name" content={ siteConfig.name } />
|
||||
@@ -38,32 +37,37 @@ const Helmet = (props) => (
|
||||
<meta name="twitter:site" content={ `@${siteConfig.twitter}` } />
|
||||
<meta name="twitter:title" content={ prepareTitle(props.title) } />
|
||||
<meta name="twitter:description" content={ prepareDescription(props.description) } />
|
||||
<meta name="twitter:image" content={ siteConfig.logo } />
|
||||
<meta name="twitter:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta name="twitter:image:alt" content="roadmap.sh" />
|
||||
|
||||
<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" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/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="/static/manifest/manifest.json" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/manifest/icon32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/manifest/icon16.png" />
|
||||
<link rel="shortcut icon" href="/static/manifest/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="/static/manifest/favicon.ico" type="image/x-icon" />
|
||||
<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" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
|
||||
{ /* Global Site Tag (gtag.js) - Google Analytics */ }
|
||||
<script async src={ `https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}` } />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${GA_TRACKING_ID}');
|
||||
`,
|
||||
}} />
|
||||
{ process.env.GA_SECRET && (
|
||||
<>
|
||||
<script async src={ `https://www.googletagmanager.com/gtag/js?id=${process.env.GA_SECRET}` } />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.GA_SECRET}');
|
||||
`,
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
|
||||
</NextHead>
|
||||
);
|
||||
|
||||
|
||||
9
components/link/badge-link.js
Normal file
9
components/link/badge-link.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export function BadgeLink({ target='_blank', variant ='success', badgeText, href, children }) {
|
||||
return (
|
||||
<p className='mb-0'>
|
||||
<a href={href} target={ target }>
|
||||
<span style={{ position: 'relative', top: '-2px'}} className={`badge badge-${variant}`}>{badgeText}</span> { children }
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
13
components/link/index.js
Normal file
13
components/link/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StrongLink = styled.a`
|
||||
border-bottom: 2px solid currentColor;
|
||||
position: relative;
|
||||
transition: background-color 120ms;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
11
components/mark/index.js
Normal file
11
components/mark/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Mark = styled.span`
|
||||
background: #1b1e21;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 4px;
|
||||
padding: 0 6px 0 7px;
|
||||
`;
|
||||
10
components/md-renderer/index.js
Normal file
10
components/md-renderer/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
|
||||
const MdRenderer = (props) => (
|
||||
<MDXProvider components={ MdxComponents }>
|
||||
{ props.children }
|
||||
</MDXProvider>
|
||||
);
|
||||
|
||||
export default MdRenderer;
|
||||
18
components/md-renderer/mdx-components/a.js
Normal file
18
components/md-renderer/mdx-components/a.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const EnrichedLink = props => {
|
||||
// Is external URL or is a media URL
|
||||
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(props.href);
|
||||
|
||||
return (
|
||||
<Link href={ props.href } target={ isExternalUrl ? '_blank' : '_self' }>
|
||||
{ props.children }
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
import Link from '../icons/link.svg';
|
||||
import Link from 'components/icons/link.svg';
|
||||
|
||||
const linkify = (Component) => {
|
||||
return (props) => {
|
||||
@@ -36,7 +36,7 @@ const H1 = styled.h1`
|
||||
position: relative;
|
||||
font-size: 42px;
|
||||
font-weight: 700;
|
||||
margin: 30px 0 8px;
|
||||
margin: 32px 0 10px !important;
|
||||
|
||||
&:hover ${HeaderLink} {
|
||||
display: flex;
|
||||
@@ -44,7 +44,7 @@ const H1 = styled.h1`
|
||||
`;
|
||||
|
||||
const H2 = styled(H1).attrs({ as: 'h2' })`
|
||||
font-size: 36px;
|
||||
font-size: 32px;
|
||||
`;
|
||||
|
||||
const H3 = styled(H1).attrs({ as: 'h3' })`
|
||||
@@ -6,6 +6,7 @@ import { Table } from './table';
|
||||
import { IFrame } from './iframe';
|
||||
import { Img } from './img';
|
||||
import EnrichedLink from './a';
|
||||
import { BadgeLink } from '../../link/badge-link';
|
||||
|
||||
const MdxComponents = {
|
||||
p: P,
|
||||
@@ -16,6 +17,7 @@ const MdxComponents = {
|
||||
table: Table,
|
||||
iframe: IFrame,
|
||||
img: Img,
|
||||
BadgeLink: BadgeLink
|
||||
};
|
||||
|
||||
export default MdxComponents;
|
||||
export default MdxComponents;
|
||||
@@ -4,8 +4,8 @@ const P = styled.p`
|
||||
color: inherit;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 27px;
|
||||
margin: 16px 0;
|
||||
line-height: 26px;
|
||||
margin: 0 0 12px;
|
||||
|
||||
img + em {
|
||||
text-align: center;
|
||||
@@ -17,4 +17,4 @@ const P = styled.p`
|
||||
}
|
||||
`;
|
||||
|
||||
export default P;
|
||||
export default P;
|
||||
@@ -1,15 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const EnrichedLink = props => {
|
||||
return (
|
||||
<Link href={ props.href } target={ /^http(s)?:\/\//.test(props.href) ? '_blank' : '_self' }>
|
||||
{ props.children }
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
||||
62
components/page-footer/carbon.scss
Normal file
62
components/page-footer/carbon.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
#carbonads {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu,
|
||||
Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
#carbonads {
|
||||
display: flex;
|
||||
max-width: 330px;
|
||||
background-color: hsl(0, 0%, 98%);
|
||||
box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, .1);
|
||||
}
|
||||
|
||||
#carbonads a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#carbonads a:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#carbonads span {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#carbonads .carbon-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.carbon-img {
|
||||
display: block;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.carbon-img img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.carbon-text {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.carbon-poweredby {
|
||||
display: block;
|
||||
padding: 8px 10px;
|
||||
background: repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, .025) 5px, hsla(0, 0%, 0%, .025) 10px) hsla(203, 11%, 95%, .4);
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .5px;
|
||||
font-weight: 600;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
41
components/page-footer/custom-ad.jsx
Normal file
41
components/page-footer/custom-ad.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
export const CustomAd = () => {
|
||||
return (
|
||||
<div id="carbonads">
|
||||
<span>
|
||||
<span className="carbon-wrap">
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-img"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
<img
|
||||
src="/fm-img.png"
|
||||
alt="FM Logo"
|
||||
border="0"
|
||||
height="100"
|
||||
width="130"
|
||||
style={{ maxWidth: "130px" }}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-text"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
He Went from ZERO TO $74,000 as a Full Time Developer in 7 Weeks
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/sponsors/kamranahmedse"
|
||||
className="carbon-poweredby"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
Sponsored by
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from 'next/link';
|
||||
import siteConfig from "data/site";
|
||||
import { FooterWrap } from './style.js'
|
||||
import siteConfig from 'content/site';
|
||||
import { FooterWrap } from './style.js';
|
||||
import './carbon.scss';
|
||||
import { CustomAd } from "./custom-ad";
|
||||
|
||||
const PageFooter = () => (
|
||||
<FooterWrap className="border-top">
|
||||
@@ -9,7 +10,7 @@ const PageFooter = () => (
|
||||
<div className="site-meta-wrap col-12 col-sm-12 col-lg col-xl col-md-12">
|
||||
<div className="site-meta">
|
||||
<div className="brand-detail">
|
||||
<Link href="/"><a className='brand'><img src="/static/brand.png" alt="" /> roadmap.sh</a></Link>
|
||||
<a href="/" className='brand'><img src="/brand.png" alt="" /> roadmap.sh</a>
|
||||
<span className="preposition">by</span>
|
||||
<a href="https://twitter.com/kamranahmedse" target="_blank" className='follow-author'>@kamranahmedse</a>
|
||||
</div>
|
||||
@@ -27,18 +28,19 @@ const PageFooter = () => (
|
||||
<div className="site-contribute foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className='foot-header'>Contribute</li>
|
||||
<li><a href='https://gum.co/roadmap-sh' target="_blank">Sponsor us</a></li>
|
||||
<li><a href={ siteConfig.url.addGuide } target="_blank">Write a Guide</a></li>
|
||||
<li><a href={ siteConfig.url.addRoadmap } target="_blank">Submit a Roadmap</a></li>
|
||||
<li><a href={ siteConfig.url.addResources } target="_blank">Add resources</a></li>
|
||||
<li><a href={ siteConfig.url.repo } target="_blank">Codebase</a></li>
|
||||
<li><a href='/about'>About this Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Learn</li>
|
||||
<li><a href="/guides">Read Guides</a></li>
|
||||
<li><a href="/roadmaps">View Roadmaps</a></li>
|
||||
<li><a href={ siteConfig.url.contribute } target="_blank">Contribute</a></li>
|
||||
<li><a href="/watch">Watch Videos</a></li>
|
||||
<li><a href="/podcasts">Podcasts</a></li>
|
||||
<li><a href="https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1" target="_blank">YouTube</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
@@ -52,6 +54,17 @@ const PageFooter = () => (
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomAd />
|
||||
|
||||
{/* Do not show on local */}
|
||||
{ process.env.GA_SECRET && false && (
|
||||
<>
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CE7DLK3Y&placement=roadmapsh" id="_carbonads_js"></script>
|
||||
{/*<div id="codefund"></div>*/}
|
||||
{/*<script src="https://app.codefund.io/properties/681/funder.js" async></script>*/}
|
||||
</>
|
||||
) }
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
padding: 65px 10px;
|
||||
padding: 65px 0;
|
||||
|
||||
.site-meta {
|
||||
margin-bottom: 30px;
|
||||
@@ -91,4 +91,4 @@ export const FooterWrap = styled.div`
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
13
components/page-logo-header/index.js
Normal file
13
components/page-logo-header/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { HeaderWrap, Subtitle, Title, Logo } from './style';
|
||||
|
||||
const PageLogoHeader = ({ title, subtitle, children, }) => (
|
||||
<HeaderWrap>
|
||||
<Logo src='/brand.png' alt='' />
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageLogoHeader;
|
||||
30
components/page-logo-header/style.js
Normal file
30
components/page-logo-header/style.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
margin-bottom: 26px;
|
||||
`;
|
||||
62
components/roadmap-header/index.js
Normal file
62
components/roadmap-header/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faArrowLeft, faClock, faEnvelope, faHandshake } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, DarkBadge, PrimaryBadge, SecondaryBadge } from 'components/badges';
|
||||
import siteConfig from "content/site";
|
||||
import { Description, Header, Title, MenuItemLink, MenuItems } from './style';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const RoadmapHeader = ({ roadmap, page = 'landscape' }) => (
|
||||
<Header>
|
||||
<Title>{ roadmap.title }</Title>
|
||||
<Description>{ roadmap.description }</Description>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/roadmaps">
|
||||
<DarkBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faArrowLeft } />
|
||||
Other Roadmaps
|
||||
</DarkBadge>
|
||||
</BadgeLink>
|
||||
{ roadmap.upcoming && (
|
||||
<BadgeLink href="/signup">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faClock } />
|
||||
Upcoming Roadmap
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
{ !roadmap.upcoming && (
|
||||
<BadgeLink href={ `${siteConfig.url.issue}?title=[${roadmap.title}] - Title Here` } target="_blank" className='d-none d-md-block' >
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={ faHandshake } />
|
||||
Suggest Changes
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faEnvelope } />
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
|
||||
<MenuItems className="border-bottom">
|
||||
<div className={ classNames({ 'd-none': roadmap.title.toLowerCase() !== 'frontend developer' })}>
|
||||
<Link href={ `${roadmap.url}` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'landscape', }) }>Landscape</MenuItemLink>
|
||||
</Link>
|
||||
<Link href={ `${roadmap.url}/resources` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'resources', }) }>Resources</MenuItemLink>
|
||||
</Link>
|
||||
{/*<Link href={ `${roadmap.url}/resources` } passHref>*/}
|
||||
{/* <MenuItemLink className={ classNames({ active: false, }) }>Project Ideas</MenuItemLink>*/}
|
||||
{/*</Link>*/}
|
||||
</div>
|
||||
</MenuItems>
|
||||
|
||||
</Header>
|
||||
);
|
||||
|
||||
export default RoadmapHeader;
|
||||
50
components/roadmap-header/style.js
Normal file
50
components/roadmap-header/style.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Header = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px 10px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
font-size: 48px;
|
||||
`;
|
||||
|
||||
export const Description = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444444;
|
||||
`;
|
||||
|
||||
export const MenuItems = styled.div`
|
||||
margin: 35px 0 15px;
|
||||
`;
|
||||
|
||||
export const MenuItemLink = styled.a`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5px 10px 8px;
|
||||
text-decoration: none;
|
||||
color: rgb(102, 102, 102);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
&.active, &.active:hover {
|
||||
color: #2d2d2d;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
left: 9px;
|
||||
right: 9px;
|
||||
bottom: -1px;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #111111;
|
||||
}
|
||||
`;
|
||||
35
components/roadmap-resources/index.js
Normal file
35
components/roadmap-resources/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapResources = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.resources.replace(/^\//, '');
|
||||
const ResourcesContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } page='resources' />
|
||||
<Summary className="container">
|
||||
<MdRenderer>
|
||||
<ResourcesContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapResources;
|
||||
21
components/roadmap-resources/style.js
Normal file
21
components/roadmap-resources/style.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
||||
@@ -1,93 +1,36 @@
|
||||
import Link from 'next/link';
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
SummaryContainer,
|
||||
Title,
|
||||
Description,
|
||||
Image,
|
||||
Header,
|
||||
Summary,
|
||||
VersionLink,
|
||||
VersionList,
|
||||
} from './style';
|
||||
import SharePage from 'components/share-page';
|
||||
import { BadgeLink, BadgesList, PrimaryBadge, SecondaryBadge, DarkBadge } from 'components/badges';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faEnvelope, faClock, faHandshake, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import siteConfig from "data/site";
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const isActiveRoadmap = (loadedVersion, roadmapVersion) => (
|
||||
(loadedVersion === roadmapVersion) ||
|
||||
(loadedVersion === 'latest' && parseInt(roadmapVersion, 10) === (new Date()).getFullYear())
|
||||
);
|
||||
const RoadmapSummary = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const UpcomingGuide = require(`../../data/roadmaps/upcoming.md`).default;
|
||||
const filePath = roadmap.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
const RoadmapSummary = ({ roadmap }) => (
|
||||
<SummaryContainer>
|
||||
<Header>
|
||||
<Title>{ roadmap.title }</Title>
|
||||
<Description>{ roadmap.description }</Description>
|
||||
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/roadmaps">
|
||||
<DarkBadge>
|
||||
<FontAwesomeIcon icon={faArrowLeft}/>
|
||||
Other Roadmaps
|
||||
</DarkBadge>
|
||||
</BadgeLink>
|
||||
{ roadmap.upcoming && (
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={faClock}/>
|
||||
Upcoming Roadmap
|
||||
</SecondaryBadge>
|
||||
) }
|
||||
{ !roadmap.upcoming && (
|
||||
<BadgeLink href={`${siteConfig.url.issue}?title=[${roadmap.title}] - Title Here`} target="_blank">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={faHandshake}/>
|
||||
Suggest Changes
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon icon={faEnvelope}/>
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
|
||||
<VersionList className="border-bottom">
|
||||
{ (roadmap.versions || []).map(versionItem => (
|
||||
<Link href={ `${roadmap.url}/${versionItem}` } passHref key={ versionItem }>
|
||||
<VersionLink className={ classNames({
|
||||
active: isActiveRoadmap(versionItem, roadmap.version),
|
||||
}) }>{ versionItem } Version</VersionLink>
|
||||
</Link>
|
||||
)) }
|
||||
</VersionList>
|
||||
</Header>
|
||||
<Summary>
|
||||
{
|
||||
roadmap.upcoming && (
|
||||
<GuideBody>
|
||||
<UpcomingGuide />
|
||||
</GuideBody>
|
||||
)
|
||||
}
|
||||
{
|
||||
!roadmap.upcoming && (
|
||||
<div className="container">
|
||||
<Image src={ roadmap.picture } />
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
);
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<Summary className={classNames("container", { "container-small": roadmap.isTextHeavy })}>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapSummary;
|
||||
|
||||
@@ -2,66 +2,20 @@ import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const Header = styled.div`
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px 55px;
|
||||
`;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
export const Summary = styled.div`
|
||||
text-align: center;
|
||||
padding: 0 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
font-size: 48px;
|
||||
`;
|
||||
|
||||
export const Description = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444444;
|
||||
`;
|
||||
|
||||
export const Image = styled.img`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const VersionList = styled.div`
|
||||
margin: 35px 0 15px;
|
||||
`;
|
||||
|
||||
export const VersionLink = styled.a`
|
||||
display: inline-block;
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 5px 10px 8px;
|
||||
text-decoration: none;
|
||||
color: rgb(102, 102, 102);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-transform: capitalize;
|
||||
|
||||
&.active, &.active:hover {
|
||||
color: #2d2d2d;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
left: 9px;
|
||||
right: 9px;
|
||||
bottom: -1px;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #111111;
|
||||
}
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ItemTitle = styled.h3`
|
||||
export const Badge = styled.span`
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
background: #696969;
|
||||
background: #8a8a8a;
|
||||
color: white;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
21
components/share-icon/index.js
Normal file
21
components/share-icon/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ShareIcon = styled.a`
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
height: 22px !important;
|
||||
width: 22px !important;
|
||||
color: #757575;
|
||||
transition: all 0.2s;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
color: #000000
|
||||
}
|
||||
`;
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faRedditSquare, faTwitterSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faFacebookSquare, faHackerNewsSquare, faRedditSquare, faTwitterSquare } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
import { getFacebookShareUrl, getRedditShareUrl, getTwitterShareUrl } from "lib/url";
|
||||
import { ShareIcon, ShareIconsList, ShareWrap } from './style';
|
||||
import { getFacebookShareUrl, getHnShareUrl, getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import { ShareIconsList, ShareWrap } from './style';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
|
||||
const SharePage = ({
|
||||
url,
|
||||
@@ -10,19 +11,22 @@ const SharePage = ({
|
||||
twitterUsername,
|
||||
}) => (
|
||||
<ShareWrap>
|
||||
<ShareIconsList className="d-sm-none d-md-none d-lg-flex d-xl-flex">
|
||||
<ShareIconsList className="d-none d-sm-flex">
|
||||
<ShareIcon
|
||||
href={ getTwitterShareUrl({
|
||||
text: `${title}${twitterUsername ? `by @${twitterUsername}` : ''}`,
|
||||
text: `${title} ${twitterUsername ? `by @${twitterUsername}` : ''}`,
|
||||
url: url
|
||||
})}
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: title, url: url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getHnShareUrl({ text: title, url: url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faHackerNewsSquare}/>
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: title, url: url })} target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
|
||||
@@ -6,7 +6,7 @@ export const ShareIconsList = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
top: 65px;
|
||||
`;
|
||||
|
||||
export const ShareWrap = styled.div`
|
||||
@@ -15,26 +15,7 @@ export const ShareWrap = styled.div`
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
padding: 0 0;
|
||||
top: 6px;
|
||||
left: -50px;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const ShareIcon = styled.a`
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
svg {
|
||||
height: 22px !important;
|
||||
width: 22px !important;
|
||||
color: #757575;
|
||||
transition: all 0.2s;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
color: #000000
|
||||
}
|
||||
top: 0;
|
||||
left: -40px;
|
||||
min-height: 100%;
|
||||
`;
|
||||
|
||||
@@ -7,7 +7,7 @@ const SignUpForm = () => (
|
||||
<form action="https://kamranahmed.us9.list-manage.com/subscribe/post?u=6f57b741a6a939744f1f203a0&id=f9ca4d6aee" target="_blank" method="post">
|
||||
<Textbox type="email" name="EMAIL" required placeholder="Your email" />
|
||||
<div style={{position: 'absolute', left: '-5000px'}}>
|
||||
<input type="text" name="b_6f57b741a6a939744f1f203a0_f9ca4d6aee" tabIndex="-1" value="" />
|
||||
<input type="text" name="b_6f57b741a6a939744f1f203a0_f9ca4d6aee" tabIndex="-1" />
|
||||
</div>
|
||||
<Button type="submit">Subscribe</Button>
|
||||
</form>
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import Link from 'next/link';
|
||||
import { HeaderWrap } from './style';
|
||||
|
||||
const SiteNav = () => (
|
||||
<HeaderWrap>
|
||||
<div className="top-row container">
|
||||
<div className="flex-grow-1 brand">
|
||||
<a href="/">
|
||||
<img src="/static/brand.png" alt="" />
|
||||
<div className='top-row container'>
|
||||
<div className='flex-grow-1 brand'>
|
||||
<a href='/'>
|
||||
<img src='/brand.png' alt='' />
|
||||
</a>
|
||||
</div>
|
||||
<div className="nav-links">
|
||||
<Link href="/roadmaps"><a>Roadmaps</a></Link>
|
||||
<Link href="/guides"><a>Guides</a></Link>
|
||||
<Link href="/signup"><a className="signup">Sign Up</a></Link>
|
||||
<div className='nav-links'>
|
||||
<a href='/guides'>Read</a>
|
||||
<a href='/watch'>
|
||||
Watch
|
||||
<span className='new-item' />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='ml-3 align-items-center d-none d-md-flex'>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=kamranahmedse&repo=developer-roadmap&type=star&count=true&size=large" frameBorder="0" scrolling="0" width="190px" height="30px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</HeaderWrap>
|
||||
|
||||
@@ -7,6 +7,17 @@ export const HeaderWrap = styled.div`
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.github-button {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand img {
|
||||
@@ -18,6 +29,7 @@ export const HeaderWrap = styled.div`
|
||||
|
||||
.nav-links {
|
||||
a {
|
||||
position: relative;
|
||||
padding: 0 10px;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
@@ -35,5 +47,15 @@ export const HeaderWrap = styled.div`
|
||||
background: #2d2d2d;
|
||||
}
|
||||
}
|
||||
|
||||
.new-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
background: #e25712;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
26
components/sponsor-banner/index.js
Normal file
26
components/sponsor-banner/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { BannerWrap, CloseSponsor, SponsorLogo } from './style';
|
||||
|
||||
export function SponsorBanner({ onCloseBanner = () => null }) {
|
||||
return (
|
||||
<div className='row'>
|
||||
<div className='col p-0'>
|
||||
<BannerWrap
|
||||
href={`https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1`}
|
||||
target='_blank'
|
||||
className='alert alert-info'
|
||||
>
|
||||
<SponsorLogo src='/sponsors/youtube.svg' />
|
||||
We now have a youtube channel. <span className='d-none d-sm-inline-block'>Subscribe for the video content.</span>
|
||||
|
||||
<CloseSponsor
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onCloseBanner();
|
||||
}}
|
||||
className='close'>×</CloseSponsor>
|
||||
</BannerWrap>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
components/sponsor-banner/style.js
Normal file
49
components/sponsor-banner/style.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BannerWrap = styled.a`
|
||||
margin-bottom: 0;
|
||||
//background: #101010;
|
||||
//color: white;
|
||||
background: #ffe0b2;
|
||||
color: #d8362a;
|
||||
font-weight: 600;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 10px 15px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
transition: all 200ms;
|
||||
|
||||
&:hover {
|
||||
background: #ffd698;
|
||||
color: #bd2015;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SponsorLogo = styled.img`
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
`;
|
||||
|
||||
export const EmojiWrap = styled.img`
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin: 0 6px 0 6px;
|
||||
`;
|
||||
|
||||
export const CloseSponsor = styled.span`
|
||||
text-shadow: none;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
color: #101010;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: #101010;
|
||||
}
|
||||
`;
|
||||
42
content/authors.json
Normal file
42
content/authors.json
Normal file
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"username": "kamranahmedse",
|
||||
"name": "Kamran Ahmed",
|
||||
"twitter": "kamranahmedse",
|
||||
"picture": "/authors/kamranahmedse.jpeg",
|
||||
"bio": "Lead engineer at Tajawal. Lover of all things web and opensource. Created roadmap.sh to help the confused ones."
|
||||
},
|
||||
{
|
||||
"username": "jesse",
|
||||
"name": "Jesse Li",
|
||||
"twitter": "__jesse_li",
|
||||
"picture": "/authors/jesse.png",
|
||||
"bio": "Software engineer."
|
||||
},
|
||||
{
|
||||
"username": "dmytrobol",
|
||||
"name": "Dmytro Bolkachov",
|
||||
"twitter": "dmytrobol",
|
||||
"picture": "/authors/dmytrobol.png",
|
||||
"bio": "JavaScript Lad, Movie buff and coder interested in everything web related"
|
||||
},
|
||||
{
|
||||
"username": "spekulatius",
|
||||
"name": "Peter Thaleikis",
|
||||
"twitter": "spekulatius1984",
|
||||
"picture": "/authors/spekulatius.jpg",
|
||||
"bio": "Developer building side-projects for fun, lover of the web and open source"
|
||||
},
|
||||
{
|
||||
"username": "ebrahimbharmal007",
|
||||
"name": "Ebrahim Bharmal",
|
||||
"twitter": "BharmalEbrahim",
|
||||
"picture": "/authors/ebrahimbharmal007.png",
|
||||
"bio": "Love building projects using tools completely new to me. Python forever. Senior at University of Texas at Arlington (2021)"
|
||||
},
|
||||
{
|
||||
"username": "lesovsky",
|
||||
"name": "Alexey Lesovsky",
|
||||
"bio": "Linux system administrator and PostgreSQL DBA at DataEgret."
|
||||
}
|
||||
]
|
||||
264
content/guides.json
Normal file
264
content/guides.json
Normal file
@@ -0,0 +1,264 @@
|
||||
[
|
||||
{
|
||||
"title": "WebStorm — Project History",
|
||||
"description": "Learn how to peek through the history of any git repository to learn how it grew.",
|
||||
"url": "/guides/project-history",
|
||||
"fileName": "project-history",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-07-16T19:59:14.191Z",
|
||||
"createdAt": "2020-07-16T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "CI and CD",
|
||||
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
|
||||
"url": "/guides/ci-cd",
|
||||
"fileName": "ci-cd",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-07-09T19:59:14.191Z",
|
||||
"createdAt": "2020-07-09T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "SSO — Single Sign On",
|
||||
"description": "Learn the basics of SAML and understand how does Single Sign On work.",
|
||||
"url": "/guides/sso",
|
||||
"fileName": "sso",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-07-01T19:59:14.191Z",
|
||||
"createdAt": "2020-07-01T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "OAuth — Open Authorization",
|
||||
"description": "Learn and understand what is OAuth and how it works",
|
||||
"url": "/guides/oauth",
|
||||
"fileName": "oauth",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-06-28T19:59:14.191Z",
|
||||
"createdAt": "2020-06-28T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "JWT Authentication",
|
||||
"description": "Understand what is JWT authentication and how is it implemented",
|
||||
"url": "/guides/jwt-authentication",
|
||||
"fileName": "jwt-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-06-20T19:59:14.191Z",
|
||||
"createdAt": "2020-06-20T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Token Based Authentication",
|
||||
"description": "Understand what is token based authentication and how it is implemented",
|
||||
"url": "/guides/token-authentication",
|
||||
"fileName": "token-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-06-02T20:59:14.191Z",
|
||||
"createdAt": "2020-06-02T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Session Based Authentication",
|
||||
"description": "Understand what is session based authentication and how it is implemented",
|
||||
"url": "/guides/session-authentication",
|
||||
"fileName": "session-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-26T20:59:14.191Z",
|
||||
"createdAt": "2020-05-26T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Basic Authentication",
|
||||
"description": "Understand what is basic authentication and how it is implemented",
|
||||
"url": "/guides/basic-authentication",
|
||||
"fileName": "basic-authentication",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-19T20:59:14.191Z",
|
||||
"createdAt": "2020-05-19T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Character Encodings",
|
||||
"description": "Covers the basics of character encodings and explains ASCII vs Unicode",
|
||||
"url": "/guides/character-encodings",
|
||||
"fileName": "character-encodings",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-14T20:59:14.191Z",
|
||||
"createdAt": "2020-05-14T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Unfamiliar Codebase",
|
||||
"description": "Tips on getting getting familiar with an unfamiliar codebase",
|
||||
"url": "/guides/unfamiliar-codebase",
|
||||
"fileName": "unfamiliar-codebase",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-05-04T20:59:14.191Z",
|
||||
"createdAt": "2020-05-04T20:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Build it and they will come?",
|
||||
"description": "Why “build it and they will come” alone won’t work anymore",
|
||||
"url": "/guides/why-build-it-and-they-will-come-wont-work-anymore",
|
||||
"fileName": "why-build-it-and-they-will-come-wont-work-anymore",
|
||||
"featured": true,
|
||||
"author": "spekulatius",
|
||||
"updatedAt": "2020-05-04T12:59:14.191Z",
|
||||
"createdAt": "2020-05-04T12:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"title": "DHCP in One Picture",
|
||||
"description": "Here is what happens when a new device joins the network.",
|
||||
"url": "/guides/dhcp-in-one-picture",
|
||||
"fileName": "dhcp-in-one-picture",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-04-28T15:48:21.191Z",
|
||||
"createdAt": "2020-04-28T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "SSL vs TLS vs SSH",
|
||||
"description": "Quick tidbit on the differences between SSL, TLS, HTTPS and SSH",
|
||||
"url": "/guides/ssl-tls-https-ssh",
|
||||
"fileName": "ssl-tls-https-ssh",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-04-22T15:48:21.191Z",
|
||||
"createdAt": "2020-04-22T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Asymptotic Notation",
|
||||
"description": "Learn the basics of measuring the time and space complexity of algorithms",
|
||||
"url": "/guides/asymptotic-notation",
|
||||
"fileName": "asymptotic-notation",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-04-03T15:48:21.191Z",
|
||||
"createdAt": "2020-04-03T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Big-O Notation",
|
||||
"description": "Easy to understand explanation of Big-O notation without any fancy terms",
|
||||
"url": "/guides/big-o-notation",
|
||||
"fileName": "big-o-notation",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-03-15T15:48:21.191Z",
|
||||
"createdAt": "2020-03-15T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Random Numbers: Are they?",
|
||||
"description": "Learn how they are generated and why they may not be truly random.",
|
||||
"url": "/guides/random-numbers",
|
||||
"fileName": "random-numbers",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-03-14T15:48:21.191Z",
|
||||
"createdAt": "2020-03-14T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Scaling Databases",
|
||||
"description": "Learn the ups and downs of different database scaling strategies",
|
||||
"url": "/guides/scaling-databases",
|
||||
"fileName": "scaling-databases",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2020-02-18T15:48:21.191Z",
|
||||
"createdAt": "2020-02-18T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "How does the internet work?",
|
||||
"description": "Learn the basics of internet and everything involved with this short video series",
|
||||
"url": "/guides/what-is-internet",
|
||||
"fileName": "what-is-internet",
|
||||
"featured": true,
|
||||
"author": "dmytrobol",
|
||||
"updatedAt": "2020-02-29T15:48:21.191Z",
|
||||
"createdAt": "2020-02-29T15:48:21.191Z"
|
||||
},
|
||||
{
|
||||
"title": "Building a BitTorrent Client",
|
||||
"description": "Learn everything you need to know about BitTorrent by writing a client in Go",
|
||||
"url": "/guides/torrent-client",
|
||||
"fileName": "torrent-client",
|
||||
"featured": true,
|
||||
"author": "jesse",
|
||||
"updatedAt": "2020-01-17T15:48:21.191Z",
|
||||
"createdAt": "2020-01-17T15:48:21.191Z",
|
||||
"canonical": "https://blog.jse.li/posts/torrent/"
|
||||
},
|
||||
{
|
||||
"title": "Levels of Seniority",
|
||||
"description": "How to Step Up as a Junior, Mid Level or a Senior Developer?",
|
||||
"url": "/guides/levels-of-seniority",
|
||||
"fileName": "levels-of-seniority",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2019-12-03T12:13:00.860Z",
|
||||
"createdAt": "2019-12-03T12:13:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Design Patterns for Humans",
|
||||
"description": "A language agnostic, ultra-simplified explanation to design patterns",
|
||||
"url": "/guides/design-patterns-for-humans",
|
||||
"fileName": "design-patterns-for-humans",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2019-10-09T12:00:00.860Z",
|
||||
"createdAt": "2019-01-23T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Journey to HTTP/2",
|
||||
"description": "The evolution of HTTP. How it all started and where we stand today",
|
||||
"url": "/guides/journey-to-http2",
|
||||
"fileName": "journey-to-http2",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "2018-12-04T12:00:00.860Z",
|
||||
"updatedAt": "2018-12-04T12:00:00.860Z",
|
||||
"draft": true
|
||||
},
|
||||
{
|
||||
"title": "DNS in One Picture",
|
||||
"description": "Quick illustrative guide on how a website is found on the internet.",
|
||||
"url": "/guides/dns-in-one-picture",
|
||||
"fileName": "dns-in-one-picture",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "2018-12-04T12:00:00.860Z",
|
||||
"createdAt": "2018-12-04T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "HTTP Caching",
|
||||
"description": "Everything you need to know about web caching",
|
||||
"url": "/guides/http-caching",
|
||||
"fileName": "http-caching",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "2018-11-29T17:00:00.860Z",
|
||||
"updatedAt": "2018-11-29T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Brief History of JavaScript",
|
||||
"description": "How JavaScript was introduced and evolved over the years",
|
||||
"url": "/guides/history-of-javascript",
|
||||
"fileName": "history-of-javascript",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "2017-10-28T17:00:00.860Z",
|
||||
"updatedAt": "2017-10-28T17:00:00.860Z"
|
||||
},
|
||||
{
|
||||
"title": "Proxy Servers",
|
||||
"description": "How do proxy servers work and what are forward and reverse proxies?",
|
||||
"url": "/guides/proxy-servers",
|
||||
"fileName": "proxy-servers",
|
||||
"featured": true,
|
||||
"author": "ebrahimbharmal007",
|
||||
"createdAt": "2020-07-24T12:40:18",
|
||||
"updatedAt": "2020-07-24T12:40:18"
|
||||
}
|
||||
]
|
||||
5
content/guides/asymptotic-notation.md
Normal file
5
content/guides/asymptotic-notation.md
Normal file
@@ -0,0 +1,5 @@
|
||||
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)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1243861514907418624) where this image was posted.
|
||||
3
content/guides/basic-authentication.md
Normal file
3
content/guides/basic-authentication.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/basic-authentication.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1261783266044063748) where this image was posted.
|
||||
5
content/guides/big-o-notation.md
Normal file
5
content/guides/big-o-notation.md
Normal file
@@ -0,0 +1,5 @@
|
||||
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)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1235708842610212864) where this image was posted.
|
||||
3
content/guides/character-encodings.md
Normal file
3
content/guides/character-encodings.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/character-encodings.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1259631582362689537) where this image was posted.
|
||||
5
content/guides/ci-cd.md
Normal file
5
content/guides/ci-cd.md
Normal file
@@ -0,0 +1,5 @@
|
||||
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)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1282806173939511298) where this image was posted.
|
||||
@@ -1257,7 +1257,7 @@ In plain words
|
||||
Wikipedia says
|
||||
> In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.
|
||||
|
||||
There are 7 types of 10 types of behavioral design patterns
|
||||
There are 10 types of behavioral design patterns
|
||||
|
||||
* [Chain of Responsibility](#-chain-of-responsibility)
|
||||
* [Command](#-command)
|
||||
@@ -1595,7 +1595,7 @@ $stationList->removeStation(new RadioStation(89)); // Will remove station 89
|
||||
```
|
||||
|
||||
👽 Mediator
|
||||
========
|
||||
--------
|
||||
|
||||
Real world example
|
||||
> A general example would be when you talk to someone on your mobile phone, there is a network provider sitting between you and them and your conversation goes through it instead of being directly sent. In this case network provider is mediator.
|
||||
3
content/guides/dhcp-in-one-picture.md
Normal file
3
content/guides/dhcp-in-one-picture.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/dhcp.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1254142557417857025) where this image was posted.
|
||||
@@ -1,5 +1,5 @@
|
||||
DNS or Domain Name System is one of the fundamental blocks of the internet. As a developer, should have at-least the basic understanding of how it works. This article is a brief introduction to what is DNS and how it works.
|
||||
DNS or Domain Name System is one of the fundamental blocks of the internet. As a developer, you should have at-least the basic understanding of how it works. This article is a brief introduction to what is DNS and how it works.
|
||||
|
||||
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)
|
||||
41
content/guides/history-of-javascript.md
Normal file
41
content/guides/history-of-javascript.md
Normal file
@@ -0,0 +1,41 @@
|
||||
Around 10 years ago, Jeff Atwood (the founder of stackoverflow) made a case that JavaScript is going to be the future and he coined the “Atwood Law” which states that *Any application that can be written in JavaScript will eventually be written in JavaScript*. Fast-forward to today, 10 years later, if you look at it it rings truer than ever. JavaScript is continuing to gain more and more adoption.
|
||||
|
||||
### JavaScript is announced
|
||||
JavaScript was initially created by [Brendan Eich](https://twitter.com/BrendanEich) of NetScape and was first announced in a press release by Netscape in 1995. It has a bizarre history of naming; initally it was named `Mocha` by the creator, which was later renamed to `LiveScript`. In 1996, about a year later after the release, NetScape decided to rename it to be `JavaScript` with hopes of capitalizing on the Java community (although JavaScript did not have any relationship with Java) and released Netscape 2.0 with the official support of JavaScript.
|
||||
|
||||
### ES1, ES2 and ES3
|
||||
In 1996, Netscape decided to submit it to [ECMA International](https://en.wikipedia.org/wiki/Ecma_International) with the hopes of getting it standardized. First edition of the standard specification was released in 1997 and the language was standardized. After the initial release, `ECMAScript` was continued to be worked upon and in no-time two more versions were released ECMAScript 2 in 1998 and ECMAScript 3 in 1999.
|
||||
|
||||
### Decade of Silence and ES4
|
||||
After the release of ES3 in 1999, there was a complete silence for a decade and no changes were made to the official standard. There was some work on the fourth edition in the initial days; some of the features that were being discussed included classes, modules, static typings, destructuring etc. It was being targeted to be released by 2008 but was abandoned due to political differences concerning language complexity. However, the vendors kept introducing the extensions to the language and the developers were left scratching their heads — adding polyfills to battle compatibility issues between different browsers.
|
||||
|
||||
### From silence to ES5
|
||||
Google, Microsoft, Yahoo and other disputers of ES4 came together and decided to work on a less ambitious update to ES3 tentatively named ES3.1. But the teams were still fighting about what to include from ES4 and what not. Finally, in 2009 ES5 was released mainly focusing on fixing the compatibility and security issues etc. But there wasn’t much of a splash in the water — it took ages for the vendors to incorporate the standards and many developers were still using ES3 without being aware of the “modern” standards.
|
||||
|
||||
### Release of ES6 — ECMAScript 2015
|
||||
After a few years of the release of ES5, things started to change, TC39 (the committee under ECMA international responsible for ECMAScript standardization) kept working on the next version of ECMAScript (ES6) which was originally named ES Harmony, before being eventually released with the name ES2015. ES2015 adds significant features and syntactic sugar to allow writing complex applications. Some of the features that ES6 has to offer, include Classes, Modules, Arrows, Enhanced object literals, Template strings, Destructuring, Default param values + rest + spread, Let and Const, Iterators + for..of, Generators, Maps + Sets, Proxies, Symbols, Promises, math + number + string + array + object APIs [etc](http://es6-features.org/#Constants)
|
||||
|
||||
Browser support for ES6 is still scarce but everything that ES6 has to offer is still available to developers by transpiling the ES6 code to ES5. With the release of 6th version of ECMAScript, TC39 decided to move to yearly model of releasing updates to ECMAScript so to make sure that the new features are added as soon as they are approved and we don’t have to wait for the full specification to be drafted and approved — thus 6th version of ECMAScript was renamed as ECMAScript 2015 or ES2015 before the release in June 2015. And the next versions of ECMAScript were decided to published in June of every year.
|
||||
|
||||
### Release of ES7 — ECMAScript 2016
|
||||
In June 2016, seventh version of ECMAScript was released. As ECMAScript has been moved to an yearly release model, ECMAScript 2016 (ES2016) comparatively did not have much to offer. ES2016 includes just two new features
|
||||
|
||||
* Exponentiation operator `**`
|
||||
* `Array.prototype.includes`
|
||||
|
||||
### Release of ES8 — ECMAScript 2017
|
||||
The eighth version of ECMAScript was released in June 2017. The key highlight of ES8 was the addition of async functions. Here is the list of new features in ES8
|
||||
|
||||
* `Object.values()` and `Object.entries()`
|
||||
* String padding i.e. `String.prototype.padEnd()` and `String.prototype.padStart()`
|
||||
* `Object.getOwnPropertyDescriptors`
|
||||
* Trailing commas in function parameter lists and calls
|
||||
* Async functions
|
||||
|
||||
### What is ESNext then?
|
||||
ESNext is a dynamic name that refers to whatever the current version of ECMAScript is at the given time. For example, at the time of this writing `ES2017` or `ES8` is `ESNext`.
|
||||
|
||||
### What does the future hold?
|
||||
Since the release of ES6, [TC39](https://github.com/tc39) has quite streamlined their process. TC39 operates through a Github organization now and there are [several proposals](https://github.com/tc39/proposals) for new features or syntax to be added to the next versions of ECMAScript. Any one can go ahead and [submit a proposal](https://github.com/tc39/proposals) thus resulting in increasing the participation from the community. Every proposal goes through [four stages of maturity](https://tc39.github.io/process-document/) before it makes it into the specification.
|
||||
|
||||
And that about wraps it up. Feel free to leave your feedback in the comments section below. Also here are the links to original language specifications [ES6](https://www.ecma-international.org/ecma-262/6.0/), [ES7](https://www.ecma-international.org/ecma-262/7.0/) and [ES8](https://www.ecma-international.org/ecma-262/8.0/).
|
||||
@@ -128,7 +128,7 @@ Cache-Control: max-age=3600, no-cache, public
|
||||
```html
|
||||
Cache-Control: max-age=3600, public
|
||||
```
|
||||
it would mean that the content is publicly cacheable and will be considered stale after 60 seconds
|
||||
it would mean that the content is publicly cacheable and will be considered stale after 60 minutes
|
||||
|
||||
##### s-maxage: seconds
|
||||
**`s-maxage`** here `s-` prefix stands for shared. This directive specifically targets the shared caches. Like `max-age` it also gets the number of seconds for which something is to be cached. If present, it will override `max-age` and `expires` headers for shared caching.
|
||||
@@ -41,7 +41,7 @@ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
|
||||
Accept: */*
|
||||
```
|
||||
|
||||
As you can see, alongside the request, client has also sent it's personal information, required response type etc. While in `HTTP/0.9` client could never send such information because there were no headers.
|
||||
As you can see, alongside the request, client has also sent its personal information, required response type etc. While in `HTTP/0.9` client could never send such information because there were no headers.
|
||||
|
||||
Example response to the request above may have looked like below
|
||||
|
||||
@@ -65,7 +65,7 @@ One of the major drawbacks of `HTTP/1.0` were you couldn't have multiple request
|
||||
|
||||
#### Three-way Handshake
|
||||
|
||||
Three-way handshake in it's simples form is that all the `TCP` connections begin with a three-way handshake in which the client and the server share a series of packets before starting to share the application data.
|
||||
Three-way handshake in its simplest form is that all the `TCP` connections begin with a three-way handshake in which the client and the server share a series of packets before starting to share the application data.
|
||||
|
||||
- `SYN` - Client picks up a random number, let's say `x`, and sends it to the server.
|
||||
- `SYN ACK` - Server acknowledges the request by sending an `ACK` packet back to the client which is made up of a random number, let's say `y` picked up by server and the number `x+1` where `x` is the number that was sent by the client
|
||||
@@ -79,11 +79,11 @@ Once the three-way handshake is completed, the data sharing between the client a
|
||||
|
||||
However, some implementations of `HTTP/1.0` tried to overcome this issue by introducing a new header called `Connection: keep-alive` which was meant to tell the server "Hey server, do not close this connection, I need it again". But still, it wasn't that widely supported and the problem still persisted.
|
||||
|
||||
Apart from being connectionless, `HTTP` also is a stateless protocol i.e. server doesn't maintain the information about the client and so each of the requests has to have the information necessary for the server to fulfill the request on it's own without any association with any old requests. And so this adds fuel to the fire i.e. apart from the large number of connections that the client has to open, it also has to send some redundant data on the wire causing increased bandwidth usage.
|
||||
Apart from being connectionless, `HTTP` also is a stateless protocol i.e. server doesn't maintain the information about the client and so each of the requests has to have the information necessary for the server to fulfill the request on its own without any association with any old requests. And so this adds fuel to the fire i.e. apart from the large number of connections that the client has to open, it also has to send some redundant data on the wire causing increased bandwidth usage.
|
||||
|
||||
### HTTP/1.1 - 1999
|
||||
|
||||
After merely 3 years of `HTTP/1.0`, the next version i.e. `HTTP/1.1` was released in 1999; which made alot of improvements over it's predecessor. The major improvements over `HTTP/1.0` included
|
||||
After merely 3 years of `HTTP/1.0`, the next version i.e. `HTTP/1.1` was released in 1999; which made alot of improvements over its predecessor. The major improvements over `HTTP/1.0` included
|
||||
|
||||
- **New HTTP methods** were added, which introduced `PUT`, `PATCH`, `OPTIONS`, `DELETE`
|
||||
|
||||
@@ -111,7 +111,7 @@ After merely 3 years of `HTTP/1.0`, the next version i.e. `HTTP/1.1` was release
|
||||
|
||||
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.
|
||||
|
||||
`HTTP/1.1` was introduced in 1999 and it had been a standard for many years. Although, it improved alot over it's predecessor; with the web changing everyday, it started to show it's 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 humungous CSS/Javascript files, [domain sharding](https://www.maxcdn.com/one/visual-glossary/domain-sharding-2/) etc.
|
||||
`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 humungous CSS/Javascript files, [domain sharding](https://www.maxcdn.com/one/visual-glossary/domain-sharding-2/) etc.
|
||||
|
||||
### SPDY - 2009
|
||||
|
||||
3
content/guides/jwt-authentication.md
Normal file
3
content/guides/jwt-authentication.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/jwt-authentication.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1273375903511465990) where this image was posted.
|
||||
72
content/guides/levels-of-seniority.md
Normal file
72
content/guides/levels-of-seniority.md
Normal file
@@ -0,0 +1,72 @@
|
||||
I have been working on redoing the [roadmaps](https://roadmap.sh) – splitting the skillset based on the seniority levels to make them easier to follow and not scare the new developers away. Since the roadmaps are going to be just about the technical knowledge, I thought it would be a good idea to reiterate and have an article on what I think of different seniority roles.
|
||||
|
||||
I have seen many organizations decide the seniority of developers by giving more significance to the years of experience than they should. I have seen developers labeled "Junior" doing the work of Senior Developers and I have seen "Lead" developers who weren't even qualified to be called "Senior". The seniority of a developer cannot just be decided by their age, years of experience or technical knowledge that they have got. There are other factors in play here -- their perception of work, how they interact with their peers and how they approach problems. We discuss these three key factors in detail for each of the seniority levels below.
|
||||
|
||||
### Different Seniority Titles
|
||||
Different organizations might have different seniority titles but they mainly fall into three categories:
|
||||
|
||||
* Junior Developer
|
||||
* Mid Level Developer
|
||||
* Senior Developer
|
||||
|
||||
### Junior Developer
|
||||
Junior developers are normally fresh graduates and it's either they don't have or they have minimal industry experience. Not only they have weak coding skills but there are also a few other things that give Junior developers away:
|
||||
|
||||
* Their main mantra is "making it work" without giving much attention to how the solution is achieved. To them, a working software and good software are equivalent.
|
||||
* They usually require very specific and structured directions to achieve something. They suffer from tunnel vision, need supervision and continuous guidance to be effective team members.
|
||||
* Most of the Junior developers just try to live up to the role and, when stuck, they might leave work for a senior developer instead of at least trying to take a stab at something.
|
||||
* They don't know about the business side of the company and don't realize how management/sales/marketing/etc think and they don't realize how much rework, wasted effort, and end-user aggravation could be saved by getting to know the business domain.
|
||||
* Over-engineering is a major problem, often leading to fragility and bugs.
|
||||
* When given a problem, they often try to fix just the current problem a.k.a. fixing the symptoms instead of fixing the root problem.
|
||||
* You might notice the "[Somebody Else's Problem](https://en.wikipedia.org/wiki/Somebody_else%27s_problem)" behavior from them.
|
||||
* They don't know what or how much they don't know, thanks to the [Dunning–Kruger effect](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect).
|
||||
* They don't take initiatives and they might be afraid to work on an unfamiliar codebase.
|
||||
* They don't participate in team discussions.
|
||||
|
||||
Being a Junior developer in the team is not necessarily a bad thing; since you are just starting out, you are not expected to be a know-it-all person. However, it is your responsibility to learn, gain experience, not get stuck with the "Junior" title and improve yourself. Here are a few tips for Junior developers to help move up the ladder of seniority:
|
||||
|
||||
* All sorts of problems can be solved if you work on them long enough. Do not give up if Stack Overflow or an issue on GitHub doesn't have an answer. Saying "I am stuck, but I have tried X, Y, and Z. Do you have any pointers?" to your lead is much better than saying "This is beyond me."
|
||||
* Read a lot of code, not just code in the projects that you are working on, but reference/framework source code, open-source. Ask your fellow developers, perhaps on Reddit too, about the good open-source examples for the language/tools of your choice.
|
||||
* Do personal side-projects, share them with people, contribute to the open-source community. Reach out to people for help. You will be surprised how much support you can get from the community. I still remember my first open-source project on GitHub from around 6 years ago which was a small PHP script (a library) that fetched details for a given address from Google's Geocoding API. The codebase was super messy, it did not have any tests, did not have any linters or sniffers, and it did not have any CI because I didn't know about any of this at that time. I am not sure how but one kind soul somehow found the project, forked it, refactored it, "modernized" it, added linting, code sniffing, added CI and opened the pull request. This one pull request taught me so many things that I might have never learned that fast on my own because I was still in college, working for a small service-based company and doing just small websites all on my own without knowing what is right and what is not. This one PR on GitHub was my introduction to open-source and I owe everything to that.
|
||||
* Avoid what is known as ["Somebody Else's Problem Field"](https://en.wikipedia.org/wiki/Somebody_else%27s_problem) behavior.
|
||||
* When given a problem to solve, try to identify the root cause and fix that instead of fixing the symptoms. And remember, not being able to reproduce means not solved. It is solved when you understand why it occurred and why it no longer does.
|
||||
* Have respect for the code that was written before you. Be generous when passing judgment on the architecture or the design decisions made in the codebase. Understand that code is often ugly and weird for a reason other than incompetence. Learning to live with and thrive with legacy code is a great skill. Never assume anybody is stupid. Instead, figure out how these intelligent, well-intentioned and experienced people have come to a decision that is stupid now. Approach inheriting legacy code with an "opportunity mindset" rather than a complaining one.
|
||||
* It's okay to not know things. You don't need to be ashamed of not knowing things already. There are no stupid questions, ask however many questions that would allow you to work effectively.
|
||||
* Don't let yourself be limited by the job title that you have. Keep working on your self-improvement.
|
||||
* Do your homework. Predict what’s coming down the pipe. Be involved in the team discussions. Even if you are wrong, you will learn something.
|
||||
* Learn about the domain that you are working with. Understand the product end-to-end as an end-user. Do not assume things, ask questions and get things cleared when in doubt.
|
||||
* Learn to communicate effectively - soft skills matter. Learn how to write good emails, how to present your work, how to phrase your questions in a thoughtful manner.
|
||||
* Sit with the senior developers, watch them work, find a mentor. No one likes a know-it-all. Get hold of your ego and be humble enough to take lessons from experienced people.
|
||||
* Don't just blindly follow the advice of "experts", take it with a grain of salt.
|
||||
* If you are asked to provide an estimate for some work, do not give an answer unless you have all the details to make a reasonable estimate. If you are forced to do that, pad it 2x or more depending on how much you don't know about what needs to be done for the task to be marked 'done'.
|
||||
* Take some time to learn how to use a debugger. Debuggers are quite beneficial when navigating new, undocumented or poorly documented codebase, or to debug weird issues.
|
||||
* Avoid saying "it works on my machine" -- yes, I have heard that a lot.
|
||||
* Try to turn any feelings of inadequacy or imposter syndrome into energy to push yourself forward and increase your skills and knowledge.
|
||||
|
||||
### Mid Level Developers
|
||||
The next level after the Junior developers is Mid Level developers. They are technically stronger than the Junior developers and can work with minimal supervision. They still have some issues to address in order to jump to Senior level.
|
||||
|
||||
Intermediate developers are more competent than the Junior developer. They start to see the flaws in their old codebase. They gain the knowledge but they get trapped into the next chain i.e. messing things up while trying to do them "the right way" e.g. hasty abstractions, overuse or unnecessary usage of Design Patterns -- they may be able to provide solution faster than the Junior developers but the solution might put you into another rabbit-hole in the long run. Without supervision, they might delay the execution while trying to "do things properly". They don't know when to make tradeoffs and they still don't know when to be dogmatic and when to be pragmatic. They can easily become attached to their solution, become myopic, and be unable to take feedback.
|
||||
|
||||
Mid-level developers are quite common. Most of the organizations wrongly label them as "Senior Developers". However, they need further mentoring in order to become Senior Developers. The next section describes the responsibilities of a senior developer and how you can become one.
|
||||
|
||||
### Senior Developers
|
||||
Senior developers are the next level after the Mid-level developers. They are the people who can get things done on their own without any supervision and without creating any issues down the road. They are more mature, have gained experience by delivering both good and bad software in the past and have learned from it — they know how to be pragmatic. Here is the list of things that are normally expected of a Senior Developer:
|
||||
|
||||
* With their past experiences, mistakes made, issues faced by over-designed or under-designed software, they can foresee the problems and persuade the direction of the codebase or the architecture.
|
||||
* They don't have a "Shiny-Toy" syndrome. They are pragmatic in the execution. They can make the tradeoffs when required, and they know why. They know where to be dogmatic and where to be pragmatic.
|
||||
* They have a good picture of the field, know what the best tool for the job is in most cases (even if they don't know the tool). They have the innate ability to pick up a new tool/language/paradigm/etc in order to solve a problem that requires it.
|
||||
* They are aware they're on a team. They view it as a part of their responsibility to mentor others. This can range from pair programming with junior devs to taking un-glorious tasks of writing docs or tests or whatever else needs to be done.
|
||||
* They have a deep understanding of the domain - they know about the business side of the company and realize how management/sales/marketing/etc think and benefit from their knowledge of the business domain during the development.
|
||||
* They don't make empty complaints, they make judgments based on the empirical evidence and they have suggestions for solutions.
|
||||
* They think much more than just code - they know that their job is to provide solutions to the problems and not just to write code.
|
||||
* They have the ability to take on large ill-defined problems, define them, break them up, and execute the pieces. A senior developer can take something big and abstract, and run with it. They will come up with a few options, discuss them with the team and implement them.
|
||||
* They have respect for the code that was written before them. They are generous when passing judgment on the architecture or the design decisions made in the codebase. They approach inheriting legacy code with an "opportunity mindset" rather than a complaining one.
|
||||
* They know how to give feedback without hurting anyone.
|
||||
|
||||
### Conclusion
|
||||
All teams are made up of a mix of all these seniority roles. Being content with your role is a bad thing and you should always strive to improve yourself for the next step. This article is based on my beliefs and observations in the industry. Lots of companies care more for the years of experience to decide the seniority which is a crappy metric -- you don't gain experience just by spending years. You gain it by continuously solving different sorts of problems, irrespective of the number of years you spend in the industry. I have seen fresh graduates having no industry experience get up to speed quickly and producing work of a Senior Engineer and I have seen Senior developers labeled "senior" merely because of their age and "years of experience".
|
||||
|
||||
The most important traits that you need to have in order to step up in your career are: not settling with mediocrity, having an open mindset, being humble, learning from your mistakes, working on the challenging problems and having an opportunity mindset rather than a complaining one.
|
||||
|
||||
With that said, this post comes to an end. What are your thoughts on the levels of seniority of developers? Feel free to send improvements to this guide. Until next time, stay tuned!
|
||||
3
content/guides/oauth.md
Normal file
3
content/guides/oauth.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/oauth.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1276994010423361540) where this image was posted.
|
||||
5
content/guides/project-history.md
Normal file
5
content/guides/project-history.md
Normal file
@@ -0,0 +1,5 @@
|
||||
One of my favorite pastimes is going through the history of my favorite projects to learn how they grew over time or how certain features were implemented.
|
||||
|
||||
The image below describes how I do that in WebStorm.
|
||||
|
||||
[](/guides/project-history.png)
|
||||
47
content/guides/proxy-servers.md
Normal file
47
content/guides/proxy-servers.md
Normal file
@@ -0,0 +1,47 @@
|
||||
Internet has connected people across the world using social media and audio/video calling features along with providing an overabundance of knowledge and tools. All this comes with an inherent danger of security and privacy breaches. In this guide we will talk about **proxies** which play a vital role in mitigating these risks. We will cover the following topics in this guide:
|
||||
|
||||
- [Proxy Server](#proxy-server)
|
||||
- [Forward Proxy Server](#forward-proxy-server)
|
||||
- [Reverse Proxy Server](#reverse-proxy-server)
|
||||
- [Summary](#summary)
|
||||
|
||||
## Proxy Server
|
||||
|
||||
***Every web request which is sent from the client to a web server goes through some type of proxy server.*** A proxy server acts as a gateway between client *(you)* and the internet and separates end-users from the websites you browse. It replaces the source IP address of the web request with the proxy server's IP address and then forwards it to the web server. The web server is unaware of the client, it only sees the proxy server.
|
||||
|
||||

|
||||
> NOTE: This is not an accurate description rather just an illustration.
|
||||
|
||||
Proxy servers serve as a single point of control making it easier to enforce security policies. It also provides caching mechanism which stores the requested web pages on the proxy server to improve performance. If the requested web-page is available in cache memory then instead of forwarding the request to the web-server it will send the cached webpage back to the client. This **saves big companies thousands of dollars** by reducing load on their servers as their website is visited by millions of users every day.
|
||||
|
||||
## Forward Proxy Server
|
||||
|
||||
A forward proxy is generally implemented on the client side and **sits in front of multiple clients** or client sources. Forward proxy servers are mainly used by companies to **manage internet usage** of their employees and **restrict content**. It is also used as a **firewall** to secure company's network by blocking any request which would pose threat to the companies's network. Proxy servers are also used to **bypass geo-restriction** and browse content which might be blocked in user's country. It enables users to **browse anonymously**, as the proxy server masks their details from the website's servers.
|
||||
|
||||

|
||||
> NOTE: This is not an accurate description rather just an illustration
|
||||
|
||||
## Reverse Proxy Server
|
||||
|
||||
Reverse proxy servers are implemented on the **server side** instead of the client side. It **sits in front of multiple webservers** and manages the incoming requests by forwarding them to the web servers. It provides anonymity for the **back-end web servers and not the client**. Reverse proxy servers are generally used to perform tasks such as **authentication, content caching, and encryption/decryption** on behalf of the web server. These tasks would **hog CPU cycles** on the web server and degrade performance of the website by introducing high amount of delay in loading the webpage. Reverse proxies are also used as **load balancers** to distribute the incoming traffic efficiently among the web servers but it is **not optimized** for this task. In essence, reverse proxy server is a gateway to a web-server or group of web-servers.
|
||||
|
||||

|
||||
> NOTE: This is not an accurate description rather just an illustration. Red lines represent server's response and black lines represent initial request from client(s).
|
||||
|
||||
## Summary
|
||||
|
||||
A proxy server acts as a gateway between client *(you)* and the internet and separates end-users from the websites you browse. ***The position of the proxy server on the network determines whether it is a forward or a reverse proxy server***. Forward proxy is implemented on the client side and **sits in front of multiple clients** or client sources and forwards requests to the web server. Reverse proxy servers are implemented on the **server side** it **sits in front of multiple webservers** and manages the incoming requests by forwarding them to the web servers.
|
||||
|
||||
If all this was too much to take in, I have a simple analogy for you.
|
||||
|
||||
At a restaurant the waiter/waitress takes your order and gives it to the kitchen head chef. The head chef then calls out the order and assigns tasks to everyone in the kitchen.
|
||||
|
||||
In this analogy:
|
||||
|
||||
* You are the client
|
||||
* Your order is the web request
|
||||
* Waiter/Waitress is your forward proxy server
|
||||
* Kitchen head chef is the reverse proxy server
|
||||
* Other chefs working in the kitchen are the web servers
|
||||
|
||||
With that said our guide comes to an end. Thank you for reading and feel free to submit any updates to the guide using the links below.
|
||||
5
content/guides/random-numbers.md
Normal file
5
content/guides/random-numbers.md
Normal file
@@ -0,0 +1,5 @@
|
||||
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)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1237851549302312962) where this image was posted.
|
||||
5
content/guides/scaling-databases.md
Normal file
5
content/guides/scaling-databases.md
Normal file
@@ -0,0 +1,5 @@
|
||||
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)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1234209674003611650) where this image was posted.
|
||||
3
content/guides/session-authentication.md
Normal file
3
content/guides/session-authentication.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/session-authentication.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1264113498520465410) where this image was posted.
|
||||
3
content/guides/ssl-tls-https-ssh.md
Normal file
3
content/guides/ssl-tls-https-ssh.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/ssl-tls-https-ssh.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1252717722724642822) where this image was posted.
|
||||
3
content/guides/sso.md
Normal file
3
content/guides/sso.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/sso.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1280266408434302979) where this image was posted.
|
||||
3
content/guides/token-authentication.md
Normal file
3
content/guides/token-authentication.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/token-authentication.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1266832006782103552) where this image was posted.
|
||||
564
content/guides/torrent-client.md
Normal file
564
content/guides/torrent-client.md
Normal file
@@ -0,0 +1,564 @@
|
||||
BitTorrent is a protocol for downloading and distributing files across the Internet. In contrast with the traditional client/server relationship, in which downloaders connect to a central server (for example: watching a movie on Netflix, or loading the web page you're reading now), participants in the BitTorrent network, called **peers**, download pieces of files from *each other*—this is what makes it a **peer-to-peer** protocol. In this article we will investigate how this works, and build our own client that can find peers and exchange data between them.
|
||||
|
||||

|
||||
|
||||
The protocol evolved organically over the past 20 years, and various people and organizations added extensions for features like encryption, private torrents, and new ways of finding peers. We'll be implementing the [original spec](https://www.bittorrent.org/beps/bep_0003.html) from 2001 to keep this a weekend-sized project.
|
||||
|
||||
I'll be using a [Debian ISO](https://cdimage.debian.org/debian-cd/current/amd64/bt-cd/#indexlist) file as my guinea pig because it's big, but not huge, at 350MB. As a popular Linux distribution, there will be lots of fast and cooperative peers for us to connect to. And we'll avoid the legal and ethical issues related to downloading pirated content.
|
||||
|
||||
## Finding peers
|
||||
Here’s a problem: we want to download a file with BitTorrent, but it’s a peer-to-peer protocol and we have no idea where to find peers to download it from. This is a lot like moving to a new city and trying to make friends—maybe we’ll hit up a local pub or a meetup group! Centralized locations like these are the big idea behind trackers, which are central servers that introduce peers to each other. They’re just web servers running over HTTP, and you can find Debian’s at http://bttracker.debian.org:6969/
|
||||
|
||||

|
||||
|
||||
Of course, these central servers are liable to get raided by the feds if they facilitate peers exchanging illegal content. You may remember reading about trackers like TorrentSpy, Popcorn Time, and KickassTorrents getting seized and shut down. New methods cut out the middleman by making even **peer discovery** a distributed process. We won't be implementing them, but if you're interested, some terms you can research are **DHT**, **PEX**, and **magnet links**.
|
||||
|
||||
### Parsing a .torrent file
|
||||
A .torrent file describes the contents of a torrentable file and information for connecting to a tracker. It's all we need in order to kickstart the process of downloading a torrent. Debian's .torrent file looks like this:
|
||||
|
||||
```markdown
|
||||
d8:announce41:http://bttracker.debian.org:6969/announce7:comment35:"Debian CD from cdimage.debian.org"13:creation datei1573903810e9:httpseedsl145:https://cdimage.debian.org/cdimage/release/10.2.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.2.0-amd64-netinst.iso145:https://cdimage.debian.org/cdimage/archive/10.2.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.2.0-amd64-netinst.isoe4:infod6:lengthi351272960e4:name31:debian-10.2.0-amd64-netinst.iso12:piece lengthi262144e6:pieces26800:<3A><1F><0F><><EFBFBD>PS<50>^<5E><> (binary blob of the hashes of each piece)ee
|
||||
```
|
||||
|
||||
That mess is encoded in a format called **Bencode** (pronounced *bee-encode*), and we'll need to decode it.
|
||||
|
||||
Bencode can encode roughly the same types of structures as JSON—strings, integers, lists, and dictionaries. Bencoded data is not as human-readable/writable as JSON, but it can efficiently handle binary data and it's really simple to parse from a stream. Strings come with a length prefix, and look like `4:spam`. Integers go between *start* and *end* markers, so `7` would encode to `i7e`. Lists and dictionaries work in a similar way: `l4:spami7ee` represents `['spam', 7]`, while `d4:spami7ee` means `{spam: 7}`.
|
||||
|
||||
|
||||
In a prettier format, our .torrent file looks like this:
|
||||
|
||||
```markdown
|
||||
d
|
||||
8:announce
|
||||
41:http://bttracker.debian.org:6969/announce
|
||||
7:comment
|
||||
35:"Debian CD from cdimage.debian.org"
|
||||
13:creation date
|
||||
i1573903810e
|
||||
4:info
|
||||
d
|
||||
6:length
|
||||
i351272960e
|
||||
4:name
|
||||
31:debian-10.2.0-amd64-netinst.iso
|
||||
12:piece length
|
||||
i262144e
|
||||
6:pieces
|
||||
26800:<3A><1F><0F><><EFBFBD>PS<50>^<5E><> (binary blob of the hashes of each piece)
|
||||
e
|
||||
e
|
||||
```
|
||||
|
||||
In this file, we can spot the URL of the tracker, the creation date (as a Unix timestamp), the name and size of the file, and a big binary blob containing the SHA-1 hashes of each **piece**, which are equally-sized parts of the file we want to download. The exact size of a piece varies between torrents, but they are usually somewhere between 256KB and 1MB. This means that a large file might be made up of *thousands* of pieces. We'll download these pieces from our peers, check them against the hashes from our torrent file, assemble them together, and boom, we've got a file!
|
||||
|
||||

|
||||
|
||||
This mechanism allows us to verify the integrity of each piece as we go. It makes BitTorrent resistant to accidental corruption or intentional **torrent poisoning**. Unless an attacker is capable of breaking SHA-1 with a preimage attack, we will get exactly the content we asked for.
|
||||
|
||||
It would be really fun to write a bencode parser, but parsing isn't our focus today. But I found Fredrik Lundh's [50 line parser](https://effbot.org/zone/bencode.htm) to be especially illuminating. For this project, I used [github.com/jackpal/bencode-go](https://github.com/jackpal/bencode-go):
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/jackpal/bencode-go"
|
||||
)
|
||||
|
||||
type bencodeInfo struct {
|
||||
Pieces string `bencode:"pieces"`
|
||||
PieceLength int `bencode:"piece length"`
|
||||
Length int `bencode:"length"`
|
||||
Name string `bencode:"name"`
|
||||
}
|
||||
|
||||
type bencodeTorrent struct {
|
||||
Announce string `bencode:"announce"`
|
||||
Info bencodeInfo `bencode:"info"`
|
||||
}
|
||||
|
||||
// Open parses a torrent file
|
||||
func Open(r io.Reader) (*bencodeTorrent, error) {
|
||||
bto := bencodeTorrent{}
|
||||
err := bencode.Unmarshal(r, &bto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bto, nil
|
||||
}
|
||||
```
|
||||
|
||||
Because I like to keep my structures relatively flat, and I like to keep my application structs separate from my serialization structs, I exported a different, flatter struct named `TorrentFile` and wrote a few helper functions to convert between the two.
|
||||
|
||||
Notably, I split `pieces` (previously a string) into a slice of hashes (each `[20]byte`) so that I can easily access individual hashes later. I also computed the SHA-1 hash of the entire bencoded `info` dict (the one which contained the name, size, and piece hashes). We know this as the **infohash** and it uniquely identifies files when we talk to trackers and peers. More on this later.
|
||||
|
||||

|
||||
|
||||
```go
|
||||
type TorrentFile struct {
|
||||
Announce string
|
||||
InfoHash [20]byte
|
||||
PieceHashes [][20]byte
|
||||
PieceLength int
|
||||
Length int
|
||||
Name string
|
||||
}
|
||||
|
||||
func (bto *bencodeTorrent) toTorrentFile() (*TorrentFile, error) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving peers from the tracker
|
||||
Now that we have information about the file and its tracker, let's talk to the tracker to **announce** our presence as a peer and to retrieve a list of other peers. We just need to make a GET request to the `announce` URL supplied in the .torrent file, with a few query parameters:
|
||||
|
||||
```go
|
||||
func (t *TorrentFile) buildTrackerURL(peerID [20]byte, port uint16) (string, error) {
|
||||
base, err := url.Parse(t.Announce)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := url.Values{
|
||||
"info_hash": []string{string(t.InfoHash[:])},
|
||||
"peer_id": []string{string(peerID[:])},
|
||||
"port": []string{strconv.Itoa(int(Port))},
|
||||
"uploaded": []string{"0"},
|
||||
"downloaded": []string{"0"},
|
||||
"compact": []string{"1"},
|
||||
"left": []string{strconv.Itoa(t.Length)},
|
||||
}
|
||||
base.RawQuery = params.Encode()
|
||||
return base.String(), nil
|
||||
}
|
||||
```
|
||||
|
||||
The important ones:
|
||||
|
||||
* **info_hash**: Identifies the *file* we're trying to download. It's the infohash we calculated earlier from the bencoded `info` dict. The tracker will use this to figure out which peers to show us.
|
||||
* **peer_id**: A 20 byte name to identify *ourselves* to trackers and peers. We'll just generate 20 random bytes for this. Real BitTorrent clients have IDs like `-TR2940-k8hj0wgej6ch` which identify the client software and version—in this case, TR2940 stands for Transmission client 2.94.
|
||||
|
||||

|
||||
|
||||
### Parsing the tracker response
|
||||
We get back a bencoded response:
|
||||
|
||||
```markdown
|
||||
d
|
||||
8:interval
|
||||
i900e
|
||||
5:peers
|
||||
252:(another long binary blob)
|
||||
e
|
||||
```
|
||||
|
||||
`Interval` tells us how often we're supposed to connect to the tracker again to refresh our list of peers. A value of 900 means we should reconnect every 15 minutes (900 seconds).
|
||||
|
||||
`Peers` is another long binary blob containing the IP addresses of each peer. It's made out of **groups of six bytes**. The first four bytes in each group represent the peer's IP address—each byte represents a number in the IP. The last two bytes represent the port, as a big-endian `uint16`. **Big-endian**, or **network order**, means that we can interpret a group of bytes as an integer by just squishing them together left to right. For example, the bytes `0x1A`, `0xE1` make `0x1AE1`, or 6881 in decimal.
|
||||
|
||||

|
||||
|
||||
```go
|
||||
// Peer encodes connection information for a peer
|
||||
type Peer struct {
|
||||
IP net.IP
|
||||
Port uint16
|
||||
}
|
||||
|
||||
// Unmarshal parses peer IP addresses and ports from a buffer
|
||||
func Unmarshal(peersBin []byte) ([]Peer, error) {
|
||||
const peerSize = 6 // 4 for IP, 2 for port
|
||||
numPeers := len(peersBin) / peerSize
|
||||
if len(peersBin)%peerSize != 0 {
|
||||
err := fmt.Errorf("Received malformed peers")
|
||||
return nil, err
|
||||
}
|
||||
peers := make([]Peer, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
offset := i * peerSize
|
||||
peers[i].IP = net.IP(peersBin[offset : offset+4])
|
||||
peers[i].Port = binary.BigEndian.Uint16(peersBin[offset+4 : offset+6])
|
||||
}
|
||||
return peers, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Downloading from peers
|
||||
Now that we have a list of peers, it's time to connect with them and start downloading pieces! We can break down the process into a few steps. For each peer, we want to:
|
||||
|
||||
1. Start a TCP connection with the peer. This is like starting a phone call.
|
||||
2. Complete a two-way BitTorrent **handshake**. *"Hello?" "Hello."*
|
||||
3. Exchange **messages** to download **pieces**. *"I'd like piece #231 please."*
|
||||
|
||||
## Start a TCP connection
|
||||
```go
|
||||
conn, err := net.DialTimeout("tcp", peer.String(), 3*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
```
|
||||
|
||||
I set a timeout so that I don't waste too much time on peers that aren't going to let me connect. For the most part, it's a pretty standard TCP connection.
|
||||
|
||||
### Complete the handshake
|
||||
We've just set up a connection with a peer, but we want do a handshake to validate our assumptions that the peer
|
||||
|
||||
* can communicate using the BitTorrent protocol
|
||||
* is able to understand and respond to our messages
|
||||
* has the file that we want, or at least knows what we're talking about
|
||||
|
||||

|
||||
|
||||
My father told me that the secret to a good handshake is a firm grip and eye contact. The secret to a good BitTorrent handshake is that it's made up of five parts:
|
||||
|
||||
1. The length of the protocol identifier, which is always 19 (0x13 in hex)
|
||||
2. The protocol identifier, called the **pstr** which is always `BitTorrent protocol`
|
||||
3. Eight **reserved bytes**, all set to 0. We'd flip some of them to 1 to indicate that we support certain [extensions](http://www.bittorrent.org/beps/bep_0010.html). But we don't, so we'll keep them at 0.
|
||||
4. The **infohash** that we calculated earlier to identify which file we want
|
||||
5. The **Peer ID** that we made up to identify ourselves
|
||||
|
||||
Put together, a handshake string might look like this:
|
||||
|
||||
```markdown
|
||||
\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00\x86\xd4\xc8\x00\x24\xa4\x69\xbe\x4c\x50\xbc\x5a\x10\x2c\xf7\x17\x80\x31\x00\x74-TR2940-k8hj0wgej6ch
|
||||
```
|
||||
|
||||
After we send a handshake to our peer, we should receive a handshake back in the same format. The infohash we get back should match the one we sent so that we know that we're talking about the same file. If everything goes as planned, we're good to go. If not, we can sever the connection because there's something wrong. *"Hello?" "这是谁? 你想要什么?" "Okay, wow, wrong number."*
|
||||
|
||||
In our code, let's make a struct to represent a handshake, and write a few methods for serializing and reading them:
|
||||
|
||||
```go
|
||||
// A Handshake is a special message that a peer uses to identify itself
|
||||
type Handshake struct {
|
||||
Pstr string
|
||||
InfoHash [20]byte
|
||||
PeerID [20]byte
|
||||
}
|
||||
|
||||
// Serialize serializes the handshake to a buffer
|
||||
func (h *Handshake) Serialize() []byte {
|
||||
buf := make([]byte, len(h.Pstr)+49)
|
||||
buf[0] = byte(len(h.Pstr))
|
||||
curr := 1
|
||||
curr += copy(buf[curr:], h.Pstr)
|
||||
curr += copy(buf[curr:], make([]byte, 8)) // 8 reserved bytes
|
||||
curr += copy(buf[curr:], h.InfoHash[:])
|
||||
curr += copy(buf[curr:], h.PeerID[:])
|
||||
return buf
|
||||
}
|
||||
|
||||
// Read parses a handshake from a stream
|
||||
func Read(r io.Reader) (*Handshake, error) {
|
||||
// Do Serialize(), but backwards
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Send and receive messages
|
||||
Once we've completed the initial handshake, we can send and receive **messages**. Well, not quite—if the other peer isn't ready to accept messages, we can't send any until they tell us they're ready. In this state, we're considered **choked** by the other peer. They'll send us an **unchoke** message to let us know that we can begin asking them for data. By default, we assume that we're choked until proven otherwise.
|
||||
|
||||
Once we've been unchoked, we can then begin sending **requests** for pieces, and they can send us messages back containing pieces.
|
||||
|
||||

|
||||
|
||||
#### Interpreting messages
|
||||
A message has a length, an **ID** and a **payload**. On the wire, it looks like:
|
||||
|
||||

|
||||
|
||||
A message starts with a length indicator which tells us how many bytes long the message will be. It's a 32-bit integer, meaning it's made out of four bytes smooshed together in big-endian order. The next byte, the **ID**, tells us which type of message we're receiving—for example, a `2` byte means "interested." Finally, the optional **payload** fills out the remaining length of the message.
|
||||
|
||||
```go
|
||||
type messageID uint8
|
||||
|
||||
const (
|
||||
MsgChoke messageID = 0
|
||||
MsgUnchoke messageID = 1
|
||||
MsgInterested messageID = 2
|
||||
MsgNotInterested messageID = 3
|
||||
MsgHave messageID = 4
|
||||
MsgBitfield messageID = 5
|
||||
MsgRequest messageID = 6
|
||||
MsgPiece messageID = 7
|
||||
MsgCancel messageID = 8
|
||||
)
|
||||
|
||||
// Message stores ID and payload of a message
|
||||
type Message struct {
|
||||
ID messageID
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// Serialize serializes a message into a buffer of the form
|
||||
// <length prefix><message ID><payload>
|
||||
// Interprets `nil` as a keep-alive message
|
||||
func (m *Message) Serialize() []byte {
|
||||
if m == nil {
|
||||
return make([]byte, 4)
|
||||
}
|
||||
length := uint32(len(m.Payload) + 1) // +1 for id
|
||||
buf := make([]byte, 4+length)
|
||||
binary.BigEndian.PutUint32(buf[0:4], length)
|
||||
buf[4] = byte(m.ID)
|
||||
copy(buf[5:], m.Payload)
|
||||
return buf
|
||||
}
|
||||
```
|
||||
|
||||
To read a message from a stream, we just follow the format of a message. We read four bytes and interpret them as a `uint32` to get the **length** of the message. Then, we read that number of bytes to get the **ID** (the first byte) and the **payload** (the remaining bytes).
|
||||
|
||||
```go
|
||||
// Read parses a message from a stream. Returns `nil` on keep-alive message
|
||||
func Read(r io.Reader) (*Message, error) {
|
||||
lengthBuf := make([]byte, 4)
|
||||
_, err := io.ReadFull(r, lengthBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(lengthBuf)
|
||||
|
||||
// keep-alive message
|
||||
if length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
messageBuf := make([]byte, length)
|
||||
_, err = io.ReadFull(r, messageBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := Message{
|
||||
ID: messageID(messageBuf[0]),
|
||||
Payload: messageBuf[1:],
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Bitfields
|
||||
One of the most interesting types of message is the **bitfield**, which is a data structure that peers use to efficiently encode which pieces they are able to send us. A bitfield looks like a byte array, and to check which pieces they have, we just need to look at the positions of the *bits* set to 1. You can think of it like the digital equivalent of a coffee shop loyalty card. We start with a blank card of all `0`, and flip bits to `1` to mark their positions as "stamped."
|
||||
|
||||

|
||||
|
||||
By working with *bits* instead of *bytes*, this data structure is super compact. We can stuff information about eight pieces in the space of a single byte—the size of a `bool`. The tradeoff is that accessing values becomes a little more tricky. The smallest unit of memory that computers can address are bytes, so to get to our bits, we have to do some bitwise manipulation:
|
||||
|
||||
```go
|
||||
// A Bitfield represents the pieces that a peer has
|
||||
type Bitfield []byte
|
||||
|
||||
// HasPiece tells if a bitfield has a particular index set
|
||||
func (bf Bitfield) HasPiece(index int) bool {
|
||||
byteIndex := index / 8
|
||||
offset := index % 8
|
||||
return bf[byteIndex]>>(7-offset)&1 != 0
|
||||
}
|
||||
|
||||
// SetPiece sets a bit in the bitfield
|
||||
func (bf Bitfield) SetPiece(index int) {
|
||||
byteIndex := index / 8
|
||||
offset := index % 8
|
||||
bf[byteIndex] |= 1 << (7 - offset)
|
||||
}
|
||||
```
|
||||
|
||||
### Putting it all together
|
||||
We now have all the tools we need to download a torrent: we have a list of peers obtained from the tracker, and we can communicate with them by dialing a TCP connection, initiating a handshake, and sending and receiving messages. Our last big problems are handling the **concurrency** involved in talking to multiple peers at once, and managing the **state** of our peers as we interact with them. These are both classically Hard problems.
|
||||
|
||||
#### Managing concurrency: channels as queues
|
||||
In Go, we [share memory by communicating](https://blog.golang.org/share-memory-by-communicating), and we can think of a Go channel as a cheap thread-safe queue.
|
||||
|
||||
We'll set up two channels to synchronize our concurrent workers: one for dishing out work (pieces to download) between peers, and another for collecting downloaded pieces. As downloaded pieces come in through the results channel, we can copy them into a buffer to start assembling our complete file.
|
||||
|
||||
```go
|
||||
// Init queues for workers to retrieve work and send results
|
||||
workQueue := make(chan *pieceWork, len(t.PieceHashes))
|
||||
results := make(chan *pieceResult)
|
||||
for index, hash := range t.PieceHashes {
|
||||
length := t.calculatePieceSize(index)
|
||||
workQueue <- &pieceWork{index, hash, length}
|
||||
}
|
||||
|
||||
// Start workers
|
||||
for _, peer := range t.Peers {
|
||||
go t.startDownloadWorker(peer, workQueue, results)
|
||||
}
|
||||
|
||||
// Collect results into a buffer until full
|
||||
buf := make([]byte, t.Length)
|
||||
donePieces := 0
|
||||
for donePieces < len(t.PieceHashes) {
|
||||
res := <-results
|
||||
begin, end := t.calculateBoundsForPiece(res.index)
|
||||
copy(buf[begin:end], res.buf)
|
||||
donePieces++
|
||||
}
|
||||
close(workQueue)
|
||||
```
|
||||
|
||||
We'll spawn a worker goroutine for each peer we've received from the tracker. It'll connect and handshake with the peer, and then start retrieving work from the `workQueue`, attempting to download it, and sending downloaded pieces back through the `results` channel.
|
||||
|
||||

|
||||
|
||||
```go
|
||||
func (t *Torrent) startDownloadWorker(peer peers.Peer, workQueue chan *pieceWork, results chan *pieceResult) {
|
||||
c, err := client.New(peer, t.PeerID, t.InfoHash)
|
||||
if err != nil {
|
||||
log.Printf("Could not handshake with %s. Disconnecting\n", peer.IP)
|
||||
return
|
||||
}
|
||||
defer c.Conn.Close()
|
||||
log.Printf("Completed handshake with %s\n", peer.IP)
|
||||
|
||||
c.SendUnchoke()
|
||||
c.SendInterested()
|
||||
|
||||
for pw := range workQueue {
|
||||
if !c.Bitfield.HasPiece(pw.index) {
|
||||
workQueue <- pw // Put piece back on the queue
|
||||
continue
|
||||
}
|
||||
|
||||
// Download the piece
|
||||
buf, err := attemptDownloadPiece(c, pw)
|
||||
if err != nil {
|
||||
log.Println("Exiting", err)
|
||||
workQueue <- pw // Put piece back on the queue
|
||||
return
|
||||
}
|
||||
|
||||
err = checkIntegrity(pw, buf)
|
||||
if err != nil {
|
||||
log.Printf("Piece #%d failed integrity check\n", pw.index)
|
||||
workQueue <- pw // Put piece back on the queue
|
||||
continue
|
||||
}
|
||||
|
||||
c.SendHave(pw.index)
|
||||
results <- &pieceResult{pw.index, buf}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Managing state
|
||||
We'll keep track of each peer in a struct, and modify that struct as we read messages. It'll include data like how much we've downloaded from the peer, how much we've requested from them, and whether we're choked. If we wanted to scale this further, we could formalize this as a finite state machine. But a struct and a switch are good enough for now.
|
||||
|
||||
```go
|
||||
type pieceProgress struct {
|
||||
index int
|
||||
client *client.Client
|
||||
buf []byte
|
||||
downloaded int
|
||||
requested int
|
||||
backlog int
|
||||
}
|
||||
|
||||
func (state *pieceProgress) readMessage() error {
|
||||
msg, err := state.client.Read() // this call blocks
|
||||
switch msg.ID {
|
||||
case message.MsgUnchoke:
|
||||
state.client.Choked = false
|
||||
case message.MsgChoke:
|
||||
state.client.Choked = true
|
||||
case message.MsgHave:
|
||||
index, err := message.ParseHave(msg)
|
||||
state.client.Bitfield.SetPiece(index)
|
||||
case message.MsgPiece:
|
||||
n, err := message.ParsePiece(state.index, state.buf, msg)
|
||||
state.downloaded += n
|
||||
state.backlog--
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Time to make requests!
|
||||
Files, pieces, and piece hashes aren't the full story—we can go further by breaking down pieces into **blocks**. A block is a part of a piece, and we can fully define a block by the **index** of the piece it's part of, its byte **offset** within the piece, and its **length**. When we make requests for data from peers, we are actually requesting *blocks*. A block is usually 16KB large, meaning that a single 256 KB piece might actually require 16 requests.
|
||||
|
||||
A peer is supposed to sever the connection if they receive a request for a block larger than 16KB. However, based on my experience, they're often perfectly happy to satisfy requests up to 128KB. I only got moderate gains in overall speed with larger block sizes, so it's probably better to stick with the spec.
|
||||
|
||||
#### Pipelining
|
||||
Network round-trips are expensive, and requesting each block one by one will absolutely tank the performance of our download. Therefore, it's important to **pipeline** our requests such that we keep up a constant pressure of some number of unfulfilled requests. This can increase the throughput of our connection by an order of magnitude.
|
||||
|
||||

|
||||
|
||||
Classically, BitTorrent clients kept a queue of five pipelined requests, and that's the value I'll be using. I found that increasing it can up to double the speed of a download. Newer clients use an [adaptive](https://luminarys.com/posts/writing-a-bittorrent-client.html) queue size to better accommodate modern network speeds and conditions. This is definitely a parameter worth tweaking, and it's pretty low hanging fruit for future performance optimization.
|
||||
|
||||
```go
|
||||
// MaxBlockSize is the largest number of bytes a request can ask for
|
||||
const MaxBlockSize = 16384
|
||||
|
||||
// MaxBacklog is the number of unfulfilled requests a client can have in its pipeline
|
||||
const MaxBacklog = 5
|
||||
|
||||
func attemptDownloadPiece(c *client.Client, pw *pieceWork) ([]byte, error) {
|
||||
state := pieceProgress{
|
||||
index: pw.index,
|
||||
client: c,
|
||||
buf: make([]byte, pw.length),
|
||||
}
|
||||
|
||||
// Setting a deadline helps get unresponsive peers unstuck.
|
||||
// 30 seconds is more than enough time to download a 262 KB piece
|
||||
c.Conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
defer c.Conn.SetDeadline(time.Time{}) // Disable the deadline
|
||||
|
||||
for state.downloaded < pw.length {
|
||||
// If unchoked, send requests until we have enough unfulfilled requests
|
||||
if !state.client.Choked {
|
||||
for state.backlog < MaxBacklog && state.requested < pw.length {
|
||||
blockSize := MaxBlockSize
|
||||
// Last block might be shorter than the typical block
|
||||
if pw.length-state.requested < blockSize {
|
||||
blockSize = pw.length - state.requested
|
||||
}
|
||||
|
||||
err := c.SendRequest(pw.index, state.requested, blockSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.backlog++
|
||||
state.requested += blockSize
|
||||
}
|
||||
}
|
||||
|
||||
err := state.readMessage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return state.buf, nil
|
||||
}
|
||||
```
|
||||
|
||||
#### main.go
|
||||
This is a short one. We're almost there.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/veggiedefender/torrent-client/torrentfile"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inPath := os.Args[1]
|
||||
outPath := os.Args[2]
|
||||
|
||||
tf, err := torrentfile.Open(inPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = tf.DownloadToFile(outPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<script id="asciicast-xqRSB0Jec8RN91Zt89rbb9PcL" src="https://asciinema.org/a/xqRSB0Jec8RN91Zt89rbb9PcL.js" async></script>
|
||||
|
||||
## This isn't the full story
|
||||
For brevity, I included only a few of the important snippets of code. Notably, I left out all the glue code, parsing, unit tests, and the boring parts that build character. View my [full implementation](https://github.com/veggiedefender/torrent-client) if you're interested.
|
||||
3
content/guides/unfamiliar-codebase.md
Normal file
3
content/guides/unfamiliar-codebase.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[](/guides/unfamiliar-codebase.png)
|
||||
|
||||
Here is the [original tweet](https://twitter.com/kamranahmedse/status/1256340163573231616) where this image was posted.
|
||||
62
content/guides/what-is-internet.md
Normal file
62
content/guides/what-is-internet.md
Normal file
@@ -0,0 +1,62 @@
|
||||
Since the explosive growth of web-based applications, every developer could stand to benefit from understanding how the Internet works. In this article, accompanied with an introductory series of short videos about the Internet from [code.org](https://code.org), you will learn the basics of the Internet and how it works. After going through this article, you will be able to answer the below questions:
|
||||
|
||||
* What is the Internet?
|
||||
* How does the information move on the internet?
|
||||
* How do the networks talk to each other and the protocols involved?
|
||||
* What's the relationship between packets, routers, and reliability?
|
||||
* HTTP and the HTML – How are you viewing this webpage in your browser?
|
||||
* How is the information transfer on the internet made secure?
|
||||
* What is cybersecurity and what are some common internet crimes?
|
||||
|
||||
## What is the Internet?
|
||||
|
||||
The Internet is a global network of computers connected to each other which communicate through a standardized set of protocols.
|
||||
|
||||
In the video below, Vint Cerf, one of the "fathers of the internet," explains the history of how the Internet works and how no one person or organization is really in charge of it.
|
||||
|
||||
<iframe width="100%" height="400" src="https://www.youtube.com/embed/Dxcc6ycZ73M" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
|
||||
## Wires, Cables, and Wi-Fi
|
||||
|
||||
Information on the Internet moves from computer to another in the form of bits over various mediums, including Ethernet cables, fiber optic cables, and wireless signals (i.e., radio waves).
|
||||
|
||||
In the video linked below, you will learn about the different mediums for data transfer on the Internet and the pros and cons for each.
|
||||
|
||||
<iframe width="100%" height="400" src="https://www.youtube.com/embed/ZhEf7e4kopM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## IP Addresses and DNS
|
||||
|
||||
Now that you know about the physical medium for the data transfer over the internet, it's time to learn about the protocols involved. How does the information traverse from one computer to another in this massive global network of computers?
|
||||
|
||||
In the video below, you will get a brief introduction to IP, DNS, and how these protocols make the Internet work.
|
||||
|
||||
<iframe width="100%" height="400" src="https://www.youtube.com/embed/5o8CwafCxnU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## Packets, Routing, and Reliability
|
||||
|
||||
Information transfer on the Internet from one computer to another does not need to follow a fixed path; in fact, it may change paths during the transfer. This information transfer is done in the form of packets and these packets may follow different routes depending on certain factors.
|
||||
|
||||
In this video, you will learn about how the packets of information are routed from one computer to another to reach the destination.
|
||||
|
||||
<iframe width="100%" height="400" src="https://www.youtube.com/embed/AYdF7b3nMto" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## HTTP and HTML
|
||||
|
||||
HTTP is the standard protocol by which webpages are transferred over the Internet. The video below is a brief introduction to HTTP and how web browsers load websites for you.
|
||||
|
||||
<iframe width="100%" height="400" src="https://www.youtube.com/embed/kBXQZMmiA4s" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## Encryption and Public Keys
|
||||
|
||||
Cryptography is what keeps our communication secure on the Internet. In this short video, you will learn the basics of cryptograpy, SSL/TLS, and how they help make the communication on the Internet secure.
|
||||
|
||||
<iframe width="100%" height="400" src="https://www.youtube.com/embed/ZghMPWGXexs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## Cybersecurity and Crime
|
||||
|
||||
In this video, you will learn about the basics of cybersecurity and common cybercrimes
|
||||
|
||||
<iframe 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/).
|
||||
@@ -0,0 +1,69 @@
|
||||
We all have heard the mantra *"build it and they will come"* many times. Stories of people building a startup or project and seemingly stumbling upon a goldmine aren't few, but they aren't the rule. These stories are still the exception in the mass of launched projects and startups.
|
||||
|
||||
Before the [Wright brothers](https://en.wikipedia.org/wiki/Wright_brothers) built their Kitty Hawk, people generally believed heavy objects could not fly - physics simply forbade it. The idea to regularly board airplanes as we do it these days was unthinkable. It was considered an unrealistic daydream for humans to ever claim the sky. When the first airplanes took off, people were fascinated, of course. It was a topic people continued to talk about for ages. Technology had made something impossible possible. While the wording "build it and they will come" originated from the movie [Field of Dreams](https://en.wikipedia.org/wiki/Field_of_Dreams), this and similar historic events gave birth to the idea behind it.
|
||||
|
||||
The engineers' and inventors' dreams came true: spend time doing what you love while the success follows magically. The internet and web-standards democratized access to this dream. But with it, the idea behind it faded and became less and less powerful. In 2020, there are very strong signs the popular saying isn't correct anymore.
|
||||
|
||||
|
||||
Why doesn't "build it and they will come" work anymore?
|
||||
-------------------------------------------------------
|
||||
|
||||
There are a few reasons working hard to make "build it and they will come" a thing of the past. This being said, it doesn't mean you can't succeed building a side-project anymore. You've just got to adjust the way you are building it.
|
||||
|
||||
### Building got much easier
|
||||
|
||||
As a software engineer, some websites are a blessing. Most of us couldn't work without GitHub, Stackoverflow and of course Google, ahem, DuckDuckGo. These powerful sites help us to solve problems, learn new techniques and find the right libraries to make building projects easier. If any of these sites are down, most engineers take a break and go for a coffee instead of trying to continue working. Combine this with more sophisticated web-standards and easier access to tooling, and you arrive at a world where building projects isn't just a job for highly specialist developers anymore. Powerful frameworks such as [Laravel](https://laravel.com/) and [Quasar Framework](https://quasar.dev/) are available for anyone to build projects on - for free.
|
||||
|
||||
In fact, building projects got to a point where some people simply build them as an exercise or hobby. If you spend some time browsing GitHub you will be surprised by the open source projects people built without any commercial goals. "Low code" and "No code" are the next wave of people building projects with less technological background.
|
||||
|
||||
### Too much going on: information overload
|
||||
|
||||
We are living in a world with information overload. In the online sphere, you can find a lot of useful information. But there is also a lot of noise. For each piece of information or advice you can find a number of opposing statements. This is partly due to the fact that the internet made it much easier to publish and share information. Everyone has been given a voice - for good or bad. This makes it much harder to reach potential users. Your new project probably just drowns amongst kitten videos, opinions, and news. Never has the average lifetime of published content been so low. You've got to come up with a marketing plan before setting out on the journey.
|
||||
|
||||
### Smaller Problems
|
||||
|
||||
Besides building being easier than ever before and attention being in short supply, there is another issue making the life of makers, inventors and engineers harder: today's problems are much smaller. Back when the previously mentioned Wright Brothers set out, they fascinated people with the problem they were aiming to address: flying. Unless your name is Elon, your problem is unlikely to attract many people naturally. As a solo developer or indie hacker, the chances are higher for having a much smaller problem in a niche (of a niche). With the information overload mentioned before, niches are pretty much the only way to build a side-project or startup and succeed.
|
||||
|
||||
Does sound pretty grim for inventors, developers and engineers? Well, yes and no. We've got to tweak the approach to get in front of the eye of potential users and customers.
|
||||
|
||||
|
||||
How to market your project nowadays?
|
||||
------------------------------------
|
||||
|
||||
The very first step to improving the odds of success is [idea validation](https://peterthaleikis.com/business-idea-validation/). While this sounds fairly obvious, many engineers and developers still don't validate their ideas before starting to build the MVP. The result is another stale project and wasted effort. To succeed you need to work on marketing before you start building anything. In the link mentioned before, I describe my approach to validation and collecting useful marketing information at the same time.
|
||||
|
||||
### Build your Audience first and the project after.
|
||||
|
||||
Build your audience before you build your project. Spend your time connecting with potential users, learn from their needs and talk about their problems. This will help you market your project later on. Audience first, project second. There are numerous ways to build an audience. One of the simplest and easiest is to start with a personal or [project blog](https://startupnamecheck.com/blog/how-to-start-a-small-business-blog).
|
||||
|
||||
Don't use Medium or a similar service - opt for a self-hosted blog as it allows you to build the blog freely to your needs and have decent links back to your project later on. Don't forget to add a newsletter. Newsletters are a key to reconnect in our world of short attention spans.
|
||||
|
||||
### Tool by Tool
|
||||
|
||||
Another approach is the "Tool by Tool" approach. I've first noticed this approach being used by Shopify. The team at Shopify are providing little tools such as a [logo generator](https://hatchful.shopify.com/) and release these tools free for anyone to use. This not just builds goodwill with people; it also allows Shopify to attract powerful backlinks to their projects. As developers we are in the perfect position to build such mini-tools. It boosts morale and drives attention at the same time.
|
||||
|
||||
Spend some time evaluating where your project or product will deliver value to the end-user. Look at options to split off small, independent tools. Build these and launch them before launching the whole product. This allows you to practice launching and promoting your part-projects at the same time. With each backlink to your part-projects you will enhance your ranking in Google. An example for a maker following this approach is [Kamban](https://kambanthemaker.com/) with [FlatGA](https://flatga.io/). He built FlatGA as phase one of a bigger project currently in development.
|
||||
|
||||
### Join a Maker community
|
||||
|
||||
While you are building your part-projects, don't forget to discuss the progress publicly. This helps to attract an audience around your work and makes the launches easier. You can use Twitter threads and Reddit posts to share updates. A maker community such as [makerlog](https://getmakerlog.com/) or [WIP.chat](https://wip.chat) can also extend your reach. These allow you to get instant feedback, keep yourself accountable and they will enhance your reach at the same time.
|
||||
|
||||
### Getting ready to Launch
|
||||
|
||||
Launching seems like this special moment when you release your project into the wide world. Often this moment is combined with high expectations and developers consider launching their project the key - if not only - part of their approach to marketing. While launching can help to attract some initial customers, it shouldn't be your only idea when it comes to marketing. You should also know that launching isn't a single event. You can (and should) launch again and again. Every time you launch you are increasing the chance to reach more and new customers. After the launch is before the launch.
|
||||
|
||||
### Marketing Is an On-going Fight
|
||||
|
||||
Many developers plan to launch their product on a few sites and see where it takes their project from there on. This works well, if your product goes viral by luck. A much more sustainable approach is constantly working a little on it. Marketing is most effective, if done consistently. That holds true for blogging as well as most other forms of marketing. A simple approach to keep you on the path to market your project regularly is subscribing to a free [newsletter with small marketing opportunities](https://wheretopost.email). This way, you are regularly reminded and given bite-sized tasks to complete.
|
||||
|
||||
|
||||
Closing Words
|
||||
-------------
|
||||
|
||||
I hope the article helped you to wrap your head around the idea that building side-projects alone doesn't solve any issues anymore. If you like what you've just read and want to read more, please consider subscribing to [my newsletter](https://peterthaleikis.com/newsletter). I'll send out the occasional email about interesting new articles or side-projects.
|
||||
|
||||
|
||||
About the author
|
||||
----------------
|
||||
|
||||
[Peter Thaleikis](https://peterthaleikis.com/) a software engineer and business owner. He has been developing web applications since around 2000. Before he started his own software development company [Bring Your Own Ideas Ltd.](https://bringyourownideas.com/), he has been Lead Developer for multiple organisations.
|
||||
39
content/pages/about.md
Normal file
39
content/pages/about.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## What is roadmap.sh?
|
||||
Roadmap.sh is the place containing community curated roadmaps, study plans, paths and resources for the budding developers. It started as a [set of charts to guide the developers](https://github.com/kamranahmedse/developer-roadmap) who are confused about what should they learn next but that alone wasn't enough so I expanded it into the website to get more contributors involved.
|
||||
|
||||
## What are the plans for roadmap.sh?
|
||||
The website started off as a [simple repository containing a few charts](https://github.com/kamranahmedse/developer-roadmap) for developers and based on my personal opinions but it could have been much more than that so I decided to expand it to a website where people can contribute to study plans with their areas of expertise as well, add more roadmaps, write guides etc.
|
||||
|
||||
We haven't opened up the sign ups for now but we will be doing. My long term plans for this website are to turn it into a goto place for the developers to seek guidance about their careers, help others, share their journeys, incentivize the learnings, get feedbacks on their projects etc.
|
||||
|
||||
## How did you build roadmap.sh?
|
||||
The basic version of the website has been built with [Next.js](https://github.com/zeit/next.js/), is opensource and can be found on [github](https://github.com/kamranahmedse/roadmap.sh). It was hastily done to get it out in front of the people and get people to start contributing so it might be rough on the edges, but that is where we need your help.
|
||||
|
||||
## How does it make money?
|
||||
It doesn't make any money. I have been using my personal time and budget to build it. I did not create this website with any intentions of monetization but as a good will, to help the people get out of the frustration that I was once in.
|
||||
|
||||
Having said that, I love teaching and my future plans are to be able to work full-time on roadmap.sh for which it has to make enough money to pay for my rent, groceries, bills, travel expenses, etc but even if it doesn't it's likely I'll continue growing the site however I can. My focus at the moment is not making money from it and just add content that creates value for the people.
|
||||
|
||||
> Sponsor the efforts by [paying as little as 3$ per month](http://gum.co/roadmap-sh) or with [one time payment via paypal](https://paypal.me/kamranahmedse). Alternatively, reach out to me at [kamran@roadmap.sh](mailto:kamran@roadmap.sh).
|
||||
|
||||
## Can I contribute?
|
||||
You definitely can, infact you are encouraged to do that. Even your minor contributions such as typo fixes count. The source code of the website can be [found on Github](https://github.com/kamranahmedse/roadmap.sh). Your contributions can be:
|
||||
|
||||
* Adding a new roadmap
|
||||
* Updating existing roadmap
|
||||
* Suggesting changes to the existing roadmaps
|
||||
* Writing a Guide
|
||||
* Updating an existing guide
|
||||
* Fixing grammar mistakes, typos on the website or the content
|
||||
* Updating the UI of the website
|
||||
* Refactoring the codebase
|
||||
* Becoming a sponsor
|
||||
|
||||
Just make sure to [follow the contribution guidelines](https://github.com/kamranahmedse/roadmap.sh/tree/master/contributing) when you decide to contribute.
|
||||
|
||||
## Can I redistribute the content?
|
||||
No, the license of the content on this website does not allow you to redistribute any of the content on this website anywhere. You can use it for personal use or share the link to the content if you have to but redistribution is not allowed.
|
||||
|
||||
## What is the best way to contact you?
|
||||
Tweet or send me a message [@kamranahmedse](https://twitter.com/kamranahmedse) or email me at [kamran@roadmap.sh](mailto:kamran@roadmap.sh). I get lots of messages so apologies in advance if you don't hear back from me soon but I do reply to everyone.
|
||||
|
||||
1
content/project/android-map.json
Normal file
1
content/project/android-map.json
Normal file
File diff suppressed because one or more lines are too long
12154
content/project/backend-map.json
Normal file
12154
content/project/backend-map.json
Normal file
File diff suppressed because it is too large
Load Diff
14490
content/project/devops-map.json
Normal file
14490
content/project/devops-map.json
Normal file
File diff suppressed because it is too large
Load Diff
1
content/project/disclaimer.json
Normal file
1
content/project/disclaimer.json
Normal file
@@ -0,0 +1 @@
|
||||
{"mockup":{"controls":{"control":[{"ID":"119","h":"274","measuredH":"140","measuredW":"200","properties":{"color":"16777215"},"typeID":"TextArea","w":"911","x":"223","y":"170","zOrder":"0"},{"ID":"120","measuredH":"40","measuredW":"170","properties":{"bold":"true","size":"32","text":"Disclaimer!"},"typeID":"Label","x":"245","y":"190","zOrder":"1"},{"ID":"121","measuredH":"32","measuredW":"856","properties":{"size":"24","text":"The purpose of this roadmap is to give you an idea about the landscape and to"},"typeID":"Label","x":"245","y":"246","zOrder":"2"},{"ID":"123","measuredH":"32","measuredW":"833","properties":{"size":"24","text":"guide you if you are confused about what to learn next and not to encourage"},"typeID":"Label","x":"245","y":"282","zOrder":"3"},{"ID":"124","measuredH":"32","measuredW":"825","properties":{"size":"24","text":"you to learn what is hip and trendy. You should *grow some understanding* of"},"typeID":"Label","x":"245","y":"317","zOrder":"4"},{"ID":"125","measuredH":"32","measuredW":"816","properties":{"size":"24","text":"*why one tool would be better suited for some cases than the other and*"},"typeID":"Label","x":"245","y":"353","zOrder":"5"},{"ID":"126","measuredH":"32","measuredW":"710","properties":{"size":"24","text":"*remember hip and trendy never means best suited for the job*"},"typeID":"Label","x":"245","y":"389","zOrder":"6"}]},"measuredH":"444","measuredW":"1134","mockupH":"274","mockupW":"911","version":"1.0"}}
|
||||
11822
content/project/frontend-map.json
Normal file
11822
content/project/frontend-map.json
Normal file
File diff suppressed because it is too large
Load Diff
1
content/project/intro-map.json
Normal file
1
content/project/intro-map.json
Normal file
File diff suppressed because one or more lines are too long
1
content/project/react-map.json
Normal file
1
content/project/react-map.json
Normal file
File diff suppressed because one or more lines are too long
294
content/roadmaps.json
Normal file
294
content/roadmaps.json
Normal file
@@ -0,0 +1,294 @@
|
||||
[
|
||||
{
|
||||
"seo": {
|
||||
"title": "Learn to become a modern frontend developer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for modern frontend development. Learn to become a modern frontend developer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a developer",
|
||||
"guide to becoming a frontend developer",
|
||||
"frontend developer",
|
||||
"frontend engineer",
|
||||
"frontend skills",
|
||||
"frontend development",
|
||||
"javascript developer",
|
||||
"frontend development skills",
|
||||
"frontend development skills test",
|
||||
"frontend engineer roadmap",
|
||||
"frontend developer roadmap",
|
||||
"become a frontend developer",
|
||||
"frontend developer career path",
|
||||
"javascript developer",
|
||||
"modern javascript developer",
|
||||
"node developer",
|
||||
"skills for frontend development",
|
||||
"learn frontend development",
|
||||
"what is frontend development",
|
||||
"frontend developer quiz",
|
||||
"frontend developer interview questions"
|
||||
]
|
||||
},
|
||||
"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 2021",
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
},
|
||||
"featured": true,
|
||||
"path": "/roadmaps/1-frontend/landscape.md",
|
||||
"resources": "/roadmaps/1-frontend/resources.md",
|
||||
"detailed": false,
|
||||
"versions": [
|
||||
"latest",
|
||||
"2018",
|
||||
"2017"
|
||||
],
|
||||
"contributorsCount": 3,
|
||||
"contributorsUrl": "/frontend/contributors",
|
||||
"url": "/frontend",
|
||||
"sidebar": {}
|
||||
},
|
||||
{
|
||||
"seo": {
|
||||
"title": "Learn to become a modern backend developer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for modern backend development. Learn to become a modern backend developer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a developer",
|
||||
"guide to becoming a backend developer",
|
||||
"backend developer",
|
||||
"backend engineer",
|
||||
"backend skills",
|
||||
"backend development",
|
||||
"javascript developer",
|
||||
"backend development skills",
|
||||
"backend development skills test",
|
||||
"backend engineer roadmap",
|
||||
"backend developer roadmap",
|
||||
"become a backend developer",
|
||||
"backend developer career path",
|
||||
"javascript developer",
|
||||
"modern javascript developer",
|
||||
"node developer",
|
||||
"skills for backend development",
|
||||
"learn backend development",
|
||||
"what is backend development",
|
||||
"backend developer quiz",
|
||||
"backend developer interview questions"
|
||||
]
|
||||
},
|
||||
"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 2021",
|
||||
"featured": true,
|
||||
"path": "/roadmaps/2-backend/landscape.md",
|
||||
"resources": "/roadmaps/2-backend/resources.md",
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
},
|
||||
"contributorsCount": 1,
|
||||
"contributorsUrl": "/backend/contributors",
|
||||
"url": "/backend",
|
||||
"sidebar": {}
|
||||
},
|
||||
{
|
||||
"seo": {
|
||||
"title": "DevOps Roadmap: Learn to become a DevOps Engineer or SRE",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for DevOps. Learn to become a modern DevOps engineer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a devops enginer",
|
||||
"devops roadmap",
|
||||
"sre roadmap",
|
||||
"site reliability engineer roadmap",
|
||||
"operations roles",
|
||||
"become devops",
|
||||
"devops skills",
|
||||
"modern devops skills",
|
||||
"devops skills test",
|
||||
"skills for devops",
|
||||
"learn devops",
|
||||
"what is devops",
|
||||
"what is sre",
|
||||
"devops quiz",
|
||||
"devops interview questions"
|
||||
]
|
||||
},
|
||||
"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 2021",
|
||||
"featured": true,
|
||||
"path": "/roadmaps/3-devops/landscape.md",
|
||||
"resources": "/roadmaps/3-devops/resources.md",
|
||||
"versions": [
|
||||
"latest",
|
||||
"2018",
|
||||
"2017"
|
||||
],
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
},
|
||||
"contributorsCount": 1,
|
||||
"contributorsUrl": "/devops/contributors",
|
||||
"url": "/devops",
|
||||
"sidebar": {}
|
||||
},
|
||||
{
|
||||
"seo": {
|
||||
"title": "Android Developer Roadmap: Learn to become an Android developer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for android development. Learn to become a modern Android developer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming an android developer",
|
||||
"android developer roadmap",
|
||||
"android roadmap",
|
||||
"become android developer",
|
||||
"android developer skills",
|
||||
"android skills test",
|
||||
"skills for android development",
|
||||
"learn android development",
|
||||
"what is android",
|
||||
"android quiz",
|
||||
"android interview questions"
|
||||
]
|
||||
},
|
||||
"title": "Android Developer",
|
||||
"description": "Step by step guide to becoming an Android developer",
|
||||
"featuredDescription": "Step by step guide to becoming a modern Android Developer in 2021",
|
||||
"isTextHeavy": true,
|
||||
"communityResource": true,
|
||||
"featured": true,
|
||||
"path": "/roadmaps/4-android/landscape.md",
|
||||
"resources": "/roadmaps/4-android/resources.md",
|
||||
"versions": [
|
||||
"latest",
|
||||
"2018",
|
||||
"2017"
|
||||
],
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
},
|
||||
"contributorsCount": 1,
|
||||
"contributorsUrl": "/android/contributors",
|
||||
"url": "/android",
|
||||
"sidebar": {}
|
||||
},
|
||||
{
|
||||
"seo": {
|
||||
"title": "DBA Roadmap: Learn to become a database administrator with PostgreSQL",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for DevOps. Learn to become a modern DevOps engineer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a database administrator",
|
||||
"guide to becoming a DBA",
|
||||
"dba roadmap",
|
||||
"db administrator roadmap",
|
||||
"database administrator roadmap",
|
||||
"postgresql roadmap",
|
||||
"dba skills",
|
||||
"db administrator skills",
|
||||
"become dba",
|
||||
"postgresql skills",
|
||||
"modern dba skills",
|
||||
"dba skills test",
|
||||
"skills for dba",
|
||||
"skills for database administrator",
|
||||
"learn dba",
|
||||
"what is dba",
|
||||
"database administrator quiz",
|
||||
"dba interview questions"
|
||||
]
|
||||
},
|
||||
"title": "PostgreSQL DBA",
|
||||
"description": "Step by step guide to becoming a modern PostgreSQL DBA",
|
||||
"featuredDescription": "Step by step guide to becoming a modern PostgreSQL DBA in 2021",
|
||||
"path": "/roadmaps/5-postgresql-dba/landscape.md",
|
||||
"resources": "/roadmaps/5-postgresql-dba/resources.md",
|
||||
"author": {
|
||||
"name": "Alexey Lesovsky",
|
||||
"url": "https://github.com/lesovsky"
|
||||
},
|
||||
"communityResource": true,
|
||||
"isTextHeavy": true,
|
||||
"featured": false,
|
||||
"detailed": false,
|
||||
"versions": [],
|
||||
"contributorsCount": 1,
|
||||
"contributorsUrl": "/postgresql-dba/contributors",
|
||||
"url": "/postgresql-dba",
|
||||
"sidebar": {}
|
||||
},
|
||||
{
|
||||
"seo": {
|
||||
"title": "React Developer Roadmap: Learn to become a React developer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for react development. Learn to become a modern React developer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a react developer",
|
||||
"react developer roadmap",
|
||||
"react roadmap",
|
||||
"become react developer",
|
||||
"react developer skills",
|
||||
"react skills test",
|
||||
"skills for react development",
|
||||
"learn react development",
|
||||
"what is react",
|
||||
"react quiz",
|
||||
"react interview questions"
|
||||
]
|
||||
},
|
||||
"title": "React Developer",
|
||||
"description": "Everything that is there to learn about React and the ecosystem in 2021.",
|
||||
"featuredDescription": "Everything that is there to learn about React and the ecosystem in 2021.",
|
||||
"isTextHeavy": false,
|
||||
"communityResource": false,
|
||||
"featured": true,
|
||||
"path": "/roadmaps/6-react/landscape.md",
|
||||
"resources": "/roadmaps/6-react/resources.md",
|
||||
"versions": [
|
||||
"latest",
|
||||
"2018",
|
||||
"2017"
|
||||
],
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
},
|
||||
"contributorsCount": 1,
|
||||
"contributorsUrl": "/react/contributors",
|
||||
"url": "/react",
|
||||
"sidebar": {}
|
||||
},
|
||||
{
|
||||
"seo": {
|
||||
"title": "QA Roadmap: Learn to become a modern QA engineer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for modern QA development. Learn to become a modern QA engineer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a QA engineer",
|
||||
"QA engineer",
|
||||
"QA skills",
|
||||
"QA development skills",
|
||||
"QA development skills test",
|
||||
"QA engineer roadmap",
|
||||
"become a QA engineer",
|
||||
"QA engineer career path",
|
||||
"skills for QA development",
|
||||
"what is QA engineer",
|
||||
"QA engineer quiz",
|
||||
"QA engineer interview questions"
|
||||
]
|
||||
},
|
||||
"title": "QA Engineer",
|
||||
"description": "Steps to follow in order to become a modern QA Engineer in 2021",
|
||||
"featuredDescription": "Step by step guide to becoming a modern QA Engineer in 2021",
|
||||
"upcoming": true,
|
||||
"path": "/roadmaps/7-qa/landscape.md",
|
||||
"resources": "/roadmaps/7-qa/resources.md",
|
||||
"author": {
|
||||
"name": "Anas Fitiani",
|
||||
"url": "https://github.com/anas-qa"
|
||||
},
|
||||
"contributorsCount": 1,
|
||||
"contributorsUrl": "/qa/contributors",
|
||||
"url": "/qa",
|
||||
"sidebar": {}
|
||||
}
|
||||
]
|
||||
1
content/roadmaps/1-frontend/landscape.md
Normal file
1
content/roadmaps/1-frontend/landscape.md
Normal file
@@ -0,0 +1 @@
|
||||

|
||||
45
content/roadmaps/1-frontend/meta.json
Normal file
45
content/roadmaps/1-frontend/meta.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"seo": {
|
||||
"title": "Learn to become a modern frontend developer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for modern frontend development. Learn to become a modern frontend developer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a developer",
|
||||
"guide to becoming a frontend developer",
|
||||
"frontend developer",
|
||||
"frontend engineer",
|
||||
"frontend skills",
|
||||
"frontend development",
|
||||
"javascript developer",
|
||||
"frontend development skills",
|
||||
"frontend development skills test",
|
||||
"frontend engineer roadmap",
|
||||
"frontend developer roadmap",
|
||||
"become a frontend developer",
|
||||
"frontend developer career path",
|
||||
"javascript developer",
|
||||
"modern javascript developer",
|
||||
"node developer",
|
||||
"skills for frontend development",
|
||||
"learn frontend development",
|
||||
"what is frontend development",
|
||||
"frontend developer quiz",
|
||||
"frontend developer interview questions"
|
||||
]
|
||||
},
|
||||
"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 2021",
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
},
|
||||
"featured": true,
|
||||
"path": "./landscape.md",
|
||||
"resources": "./resources.md",
|
||||
"detailed": false,
|
||||
"versions": [
|
||||
"latest",
|
||||
"2018",
|
||||
"2017"
|
||||
]
|
||||
}
|
||||
76
content/roadmaps/1-frontend/resources.md
Normal file
76
content/roadmaps/1-frontend/resources.md
Normal file
@@ -0,0 +1,76 @@
|
||||
<div className='alert alert-primary' style={{ marginBottom: '-10px'}}>
|
||||
This page is incomplete and is being worked upon. Please check back later or <a href='/signup'>subscribe</a> / <a href='https://twitter.com/kamranahmedse'>follow me on twitter</a> to get notified. Also, feel free to contribute by suggesting the resources in <a href='https://github.com/kamranahmedse/developer-roadmap'>the issues</a>.
|
||||
</div>
|
||||
|
||||
# Become a Frontend Developer
|
||||
Before I go ahead and list down the resources, please know that the roadmap and the list below is exhaustive and you don't need to know it all from the get go. For frontend development, all you need to get started with is learn some basic HTML, CSS and JavaScript and start working on projects; everything else you will learn along the way.
|
||||
|
||||
## Internet and how it works?
|
||||
|
||||
Get the basic understanding of internet, browsers, networks and other relevant knowledge.
|
||||
|
||||
* <BadgeLink badgeText='Read' href='/guides/what-is-internet'>What is Internet?</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=7_LPdttKXPc'>How the internet works in 5 minutes</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://kamranahmed.info/blog/2016/08/13/http-in-depth/'>What is HTTP and how it evolved?</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://blog.cloudflare.com/http3-the-past-present-and-future/'>HTTP/3: the past, the present, and the future</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://kinsta.com/blog/http3/'>What Is HTTP/3 – Lowdown on the Fast New UDP-Based Protocol</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/'>How Browsers Work: Behind the scenes of modern web browsers</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=Rck3BALhI5c'>DNS as Fast As Possible</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://howdns.works/'>How DNS works?</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='/guides/dns-in-one-picture'>DNS in One Picture</BadgeLink>
|
||||
|
||||
## Learn HTML
|
||||
HTML provides the skeleton of a webpage. Learn the basics of HTML; learn the basic tags, learn how to write semantic HTML, understand basic SEO, learn how to divide your pages into sections that will help you style them.
|
||||
|
||||
Please know that I have put multiple links for each resource. While you may pick something new while going through each, you don't need to go through all of them - if you feel like you have understood the concepts and are just repeating what you learnt, you may skip the resource and move to exercises section.
|
||||
|
||||
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=UB1O30fR-EE'>HTML Crash Course For Absolute Beginners</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://www.w3schools.com/html/default.asp'>W3Schools – HTML Tutorial</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=pQN-pnXPaVg'>HTML Full Course - Build a Website Tutorial</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://hacks.mozilla.org/2016/08/a-few-html-tips/'>A few HTML tips</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://hackernoon.com/six-tips-to-set-up-a-better-html-document-ud1033z3z'>Six tips to set up a better HTML document</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://www.w3schools.com/html/html5_semantic_elements.asp'>HTML Semantic Elements</BadgeLink>
|
||||
* <BadgeLink badgeText='Read' href='https://developer.mozilla.org/en-US/docs/Web/HTML/Element'>HTML elements reference</BadgeLink>
|
||||
|
||||
## Style your pages with CSS
|
||||
With the help of HTML, you create structure for your pages. CSS allows you to style your pages and make them pretty. If you take the analogy of human body, the skeleton would be the HTML, skin would be the CSS and muscles that help us move would be JavaScript - we will learn more about JavaScript in the coming sections.
|
||||
|
||||
* <BadgeLink badgeText='Read' href='https://www.w3schools.com/css/'>W3Schools – CSS Tutorial</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=yfoY53QXEnI'>CSS Crash Course For Absolute Beginners</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=Wm6CUkswsNw'>Build An HTML5 Website With A Responsive Layout</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://youtu.be/JJSoEo8JSnc?t=46'>Flexbox CSS In 20 Minutes</BadgeLink>
|
||||
|
||||
## Basics of JavaScript
|
||||
JavaScript allows you to add interactivity to your pages. Common examples that you may have seen on the websites are sliders, click interactions, popups and so on. In this section, you will learn the basics of JavaScript.
|
||||
|
||||
* <BadgeLink badgeText='Read' href='https://www.w3schools.com/js/'>W3Schools – JavaScript Tutorial</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://youtu.be/hdI2bqOjy3c?t=2'>JavaScript Crash Course for Beginners</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://youtu.be/P7t13SGytRk?t=22'>Build a Netflix Landing Page Clone with HTML, CSS & JS</BadgeLink>
|
||||
|
||||
## Version Control Systems and Git
|
||||
|
||||
Version control systems allow you to track changes to your codebase/files over time. They allow you to go back to some previous version of the codebase without any issues. Also, they help in collaborating with people working on the same code – if you’ve ever collaborated with other people on a project, you might already know the frustration of copying and merging the changes from someone else into your codebase; version control systems allow you to get rid of this issue.
|
||||
|
||||
In this section, you will learn what version control systems are and understand how to use Git which is the de facto VCS.
|
||||
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=zbKdDsNNOhg'>Version Control System Introduction</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=SWYqp7iY_Tc'>Git & GitHub Crash Course For Beginners</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://youtu.be/Y9XZQO1n_7c?t=21'>Learn Git in 20 Minutes</BadgeLink>
|
||||
|
||||
Now that you know what git is go ahead and create an account on [GitHub](https://github.com) and push everything that you do from now on to GitHub so that you can get the practice and get it reviewed from the other people in the community.
|
||||
|
||||
## Modern JavaScript
|
||||
|
||||
In this section you will learn how to use package managers and get started with the "modern JavaScript".
|
||||
|
||||
* <BadgeLink badgeText='Read' href='https://medium.com/the-node-js-collection/modern-javascript-explained-for-dinosaurs-f695e9747b70'>Modern JavaScript for Dinosaurs (Don't worry if you don't understand some parts of it)</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=8Rmj5UY5mJk'>What is NPM and how to use it</BadgeLink>
|
||||
* <BadgeLink variant='primary' badgeText='Watch' href='https://www.youtube.com/watch?v=jHDhaSSKmB0'>NPM Crash Course</BadgeLink>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
1
content/roadmaps/2-backend/landscape.md
Normal file
1
content/roadmaps/2-backend/landscape.md
Normal file
@@ -0,0 +1 @@
|
||||

|
||||
39
content/roadmaps/2-backend/meta.json
Normal file
39
content/roadmaps/2-backend/meta.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"seo": {
|
||||
"title": "Learn to become a modern backend developer",
|
||||
"description": "Community driven, articles, resources, guides, interview questions, quizzes for modern backend development. Learn to become a modern backend developer by following the steps, skills, resources and guides listed in this roadmap.",
|
||||
"keywords": [
|
||||
"guide to becoming a developer",
|
||||
"guide to becoming a backend developer",
|
||||
"backend developer",
|
||||
"backend engineer",
|
||||
"backend skills",
|
||||
"backend development",
|
||||
"javascript developer",
|
||||
"backend development skills",
|
||||
"backend development skills test",
|
||||
"backend engineer roadmap",
|
||||
"backend developer roadmap",
|
||||
"become a backend developer",
|
||||
"backend developer career path",
|
||||
"javascript developer",
|
||||
"modern javascript developer",
|
||||
"node developer",
|
||||
"skills for backend development",
|
||||
"learn backend development",
|
||||
"what is backend development",
|
||||
"backend developer quiz",
|
||||
"backend developer interview questions"
|
||||
]
|
||||
},
|
||||
"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 2021",
|
||||
"featured": true,
|
||||
"path": "./landscape.md",
|
||||
"resources": "./resources.md",
|
||||
"author": {
|
||||
"name": "Kamran Ahmed",
|
||||
"url": "https://twitter.com/kamranahmedse"
|
||||
}
|
||||
}
|
||||
7
content/roadmaps/2-backend/resources.md
Normal file
7
content/roadmaps/2-backend/resources.md
Normal file
@@ -0,0 +1,7 @@
|
||||
> **We are still preparing the resources**. Please check back later or [subscribe to get notified](/signup).
|
||||
|
||||
While we prepare the list, follow this simple advice to learn anything
|
||||
|
||||
> Just **pick a project and start working on it**, you will learn all that you need along the way.
|
||||
|
||||
**→** [All Roadmaps](/roadmaps) • [Programming guides](/guides) • [Subscribe](/signup)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user