Compare commits
25 Commits
fix/guide-
...
content/fl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ee4fd469e | ||
|
|
8c343217de | ||
|
|
266b9f8476 | ||
|
|
02994de95a | ||
|
|
cd3d7baeea | ||
|
|
66cac21034 | ||
|
|
3013779c43 | ||
|
|
f582a5fbda | ||
|
|
c6c52b7087 | ||
|
|
e69031b659 | ||
|
|
0c8269384d | ||
|
|
31aeb32b8b | ||
|
|
3261c222f6 | ||
|
|
372339d082 | ||
|
|
53309079dd | ||
|
|
18650c4eec | ||
|
|
56a6a7d550 | ||
|
|
5171207ea3 | ||
|
|
aafce97040 | ||
|
|
e0618537c1 | ||
|
|
c56e8f1369 | ||
|
|
2b479b142b | ||
|
|
06bd2407b8 | ||
|
|
70e4582df9 | ||
|
|
d0aa6001a2 |
43
.github/workflows/update-sponsors.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Update Sponsors
|
||||
|
||||
on:
|
||||
workflow_dispatch: # allow manual run
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # run daily at 00:00 UTC
|
||||
|
||||
env:
|
||||
SPONSOR_SHEET_API_KEY: ${{ secrets.SPONSOR_SHEET_API_KEY }}
|
||||
SPONSOR_SHEET_ID: ${{ secrets.SPONSOR_SHEET_ID }}
|
||||
|
||||
jobs:
|
||||
update-sponsors:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: pnpm/action-setup@v2.2.2
|
||||
with:
|
||||
version: 7.13.4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
- name: Update sponsors
|
||||
run: |
|
||||
node bin/update-sponsors.cjs
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
delete-branch: false
|
||||
branch: 'update-sponsors'
|
||||
base: 'master'
|
||||
labels: |
|
||||
sponsors
|
||||
automated pr
|
||||
reviewers: kamranahmedse
|
||||
commit-message: 'chore: update sponsors'
|
||||
title: 'Update Sponsor Banners'
|
||||
body: |
|
||||
Updates sponsor banners.
|
||||
Please review the changes and merge if everything looks good.
|
||||
@@ -1,7 +0,0 @@
|
||||
app-dist
|
||||
dist
|
||||
.idea
|
||||
.github
|
||||
public
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
overrides: [
|
||||
{
|
||||
files: '*.astro',
|
||||
options: {
|
||||
parser: 'astro',
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('prettier-plugin-astro'),
|
||||
require('prettier-plugin-tailwindcss'),
|
||||
],
|
||||
};
|
||||
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"prettier.documentSelectors": ["**/*.astro"],
|
||||
"[astro]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import rehypeExternalLinks from 'rehype-external-links';
|
||||
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://roadmap.sh/',
|
||||
site: 'https://roadmap.sh',
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: 'dracula',
|
||||
@@ -17,24 +17,6 @@ export default defineConfig({
|
||||
rehypeExternalLinks,
|
||||
{
|
||||
target: '_blank',
|
||||
rel: function (element) {
|
||||
const href = element.properties.href;
|
||||
const whiteListedStarts = [
|
||||
'/',
|
||||
'#',
|
||||
'mailto:',
|
||||
'https://github.com/kamranahmedse',
|
||||
'https://thenewstack.io',
|
||||
'https://cs.fyi',
|
||||
'https://roadmap.sh',
|
||||
];
|
||||
|
||||
if (whiteListedStarts.some((start) => href.startsWith(start))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return 'noopener noreferrer nofollow';
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
@@ -3,10 +3,7 @@ const path = require('path');
|
||||
|
||||
const CONTENT_DIR = path.join(__dirname, '../content');
|
||||
// Directory containing the best-practices
|
||||
const BEST_PRACTICE_CONTENT_DIR = path.join(
|
||||
__dirname,
|
||||
'../src/data/best-practices'
|
||||
);
|
||||
const BEST_PRACTICE_CONTENT_DIR = path.join(__dirname, '../src/best-practices');
|
||||
const bestPracticeId = process.argv[2];
|
||||
|
||||
const allowedBestPracticeId = fs.readdirSync(BEST_PRACTICE_CONTENT_DIR);
|
||||
@@ -31,10 +28,7 @@ if (!bestPracticeDirName) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const bestPracticeDirPath = path.join(
|
||||
BEST_PRACTICE_CONTENT_DIR,
|
||||
bestPracticeDirName
|
||||
);
|
||||
const bestPracticeDirPath = path.join(BEST_PRACTICE_CONTENT_DIR, bestPracticeDirName);
|
||||
const bestPracticeContentDirPath = path.join(
|
||||
BEST_PRACTICE_CONTENT_DIR,
|
||||
bestPracticeDirName,
|
||||
@@ -43,9 +37,7 @@ const bestPracticeContentDirPath = path.join(
|
||||
|
||||
// If best practice content already exists do not proceed as it would override the files
|
||||
if (fs.existsSync(bestPracticeContentDirPath)) {
|
||||
console.error(
|
||||
`Best Practice content already exists @ ${bestPracticeContentDirPath}`
|
||||
);
|
||||
console.error(`Best Practice content already exists @ ${bestPracticeContentDirPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -59,11 +51,7 @@ function prepareDirTree(control, dirTree) {
|
||||
const controlName = control?.properties?.controlName || '';
|
||||
|
||||
// No directory for a group without control name
|
||||
if (
|
||||
!controlName ||
|
||||
controlName.startsWith('check:') ||
|
||||
controlName.startsWith('ext_link:')
|
||||
) {
|
||||
if (!controlName || controlName.startsWith('check:') || controlName.startsWith('ext_link:')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,10 +76,7 @@ function prepareDirTree(control, dirTree) {
|
||||
return { dirTree };
|
||||
}
|
||||
|
||||
const bestPractice = require(path.join(
|
||||
__dirname,
|
||||
`../public/jsons/best-practices/${bestPracticeId}`
|
||||
));
|
||||
const bestPractice = require(path.join(__dirname, `../public/jsons/best-practices/${bestPracticeId}`));
|
||||
const controls = bestPractice.mockup.controls.control;
|
||||
|
||||
// Prepare the dir tree that we will be creating
|
||||
@@ -144,7 +129,11 @@ function createDirTree(parentDir, dirTree, filePaths = {}) {
|
||||
// For each of the directory names, create a
|
||||
// directory inside the given directory
|
||||
childrenDirNames.forEach((dirName) => {
|
||||
createDirTree(path.join(parentDir, dirName), dirTree[dirName], filePaths);
|
||||
createDirTree(
|
||||
path.join(parentDir, dirName),
|
||||
dirTree[dirName],
|
||||
filePaths
|
||||
);
|
||||
});
|
||||
|
||||
return filePaths;
|
||||
@@ -152,4 +141,4 @@ function createDirTree(parentDir, dirTree, filePaths = {}) {
|
||||
|
||||
// Create directories and get back the paths for created directories
|
||||
createDirTree(bestPracticeContentDirPath, dirTree);
|
||||
console.log('Created best practice content directory structure');
|
||||
console.log('Created best practice content directory structure');
|
||||
@@ -1,5 +1,4 @@
|
||||
## CLI Tools
|
||||
|
||||
> A bunch of CLI scripts to make the development easier
|
||||
|
||||
## `roadmap-links.cjs`
|
||||
@@ -10,22 +9,12 @@ Generates a list of all the resources links in any roadmap file.
|
||||
|
||||
Compresses all the JSON files in the `public/jsons` folder
|
||||
|
||||
## `update-sponsors.cjs`
|
||||
|
||||
Updates the sponsor ads on each roadmap page with the latest sponsor information in the Excel sheet.
|
||||
|
||||
## `roadmap-content.cjs`
|
||||
|
||||
Currently, for any new roadmaps that we add, we do create the interactive roadmap but we end up leaving the content empty in the roadmap till we get time to fill it up manually.
|
||||
|
||||
This script populates all the content files with some minimal content from OpenAI so that the users visiting the website have something to read in the interactive roadmap till we get time to fill it up manually.
|
||||
|
||||
## `roadmap-dirs.cjs`
|
||||
|
||||
This command is used to create the content folders and files for the interactivity of the roadmap. You can use the below command to generate the roadmap skeletons inside a roadmap directory:
|
||||
|
||||
```bash
|
||||
npm run roadmap-dirs [frontend|backend|devops|...]
|
||||
npm run roadmap-content [frontend|backend|devops|...]
|
||||
```
|
||||
|
||||
For the content skeleton to be generated, we should have proper grouping, and the group names in the project files. You can follow the steps listed below in order to add the meta information to the roadmap.
|
||||
@@ -35,3 +24,5 @@ For the content skeleton to be generated, we should have proper grouping, and th
|
||||
- Assign the name to the groups.
|
||||
- Group names have the format of `[sort]-[slug]` e.g. `100-internet`. Each group name should start with a number starting from 100 which helps with sorting of the directories and the files. Groups at the same level have the sequential sorting information.
|
||||
- Each groups children have a separate group and have the name similar to `[sort]-[parent-slug]:[child-slug]` where sort refers to the sorting of the `child-slug` and not the parent. Also parent-slug does not need to have the sorting information as a part of slug e.g. if parent was `100-internet` the children would be `100-internet:how-does-the-internet-work`, `101-internet:what-is-http`, `102-internet:browsers`.
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const OPEN_AI_API_KEY = process.env.OPEN_AI_API_KEY;
|
||||
const ALL_ROADMAPS_DIR = path.join(__dirname, '../src/data/roadmaps');
|
||||
const ROADMAP_JSON_DIR = path.join(__dirname, '../public/jsons/roadmaps');
|
||||
|
||||
const CONTENT_DIR = path.join(__dirname, '../content');
|
||||
// Directory containing the roadmaps
|
||||
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/roadmaps');
|
||||
const roadmapId = process.argv[2];
|
||||
|
||||
const allowedRoadmapIds = fs.readdirSync(ALL_ROADMAPS_DIR);
|
||||
const allowedRoadmapIds = fs.readdirSync(ROADMAP_CONTENT_DIR);
|
||||
if (!roadmapId) {
|
||||
console.error('roadmapId is required');
|
||||
process.exit(1);
|
||||
@@ -19,144 +18,146 @@ if (!allowedRoadmapIds.includes(roadmapId)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ROADMAP_CONTENT_DIR = path.join(ALL_ROADMAPS_DIR, roadmapId, 'content');
|
||||
const { Configuration, OpenAIApi } = require('openai');
|
||||
const configuration = new Configuration({
|
||||
apiKey: OPEN_AI_API_KEY,
|
||||
// Directory holding the roadmap content files
|
||||
const roadmapDirName = fs
|
||||
.readdirSync(ROADMAP_CONTENT_DIR)
|
||||
.find((dirName) => dirName.replace(/\d+-/, '') === roadmapId);
|
||||
|
||||
if (!roadmapDirName) {
|
||||
console.error('Roadmap directory not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
|
||||
const roadmapContentDirPath = path.join(
|
||||
ROADMAP_CONTENT_DIR,
|
||||
roadmapDirName,
|
||||
'content'
|
||||
);
|
||||
|
||||
// If roadmap content already exists do not proceed as it would override the files
|
||||
if (fs.existsSync(roadmapContentDirPath)) {
|
||||
console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function prepareDirTree(control, dirTree, dirSortOrders) {
|
||||
// Directories are only created for groups
|
||||
if (control.typeID !== '__group__') {
|
||||
return;
|
||||
}
|
||||
|
||||
// e.g. 104-testing-your-apps:other-options
|
||||
const controlName = control?.properties?.controlName || '';
|
||||
// e.g. 104
|
||||
const sortOrder = controlName.match(/^\d+/)?.[0];
|
||||
|
||||
// No directory for a group without control name
|
||||
if (!controlName || !sortOrder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// e.g. testing-your-apps:other-options
|
||||
const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '');
|
||||
// e.g. ['testing-your-apps', 'other-options']
|
||||
const dirParts = controlNameWithoutSortOrder.split(':');
|
||||
|
||||
// Nest the dir path in the dirTree
|
||||
let currDirTree = dirTree;
|
||||
dirParts.forEach((dirPart) => {
|
||||
currDirTree[dirPart] = currDirTree[dirPart] || {};
|
||||
currDirTree = currDirTree[dirPart];
|
||||
});
|
||||
|
||||
dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);
|
||||
|
||||
const childrenControls = control.children.controls.control;
|
||||
// No more children
|
||||
if (childrenControls.length) {
|
||||
childrenControls.forEach((childControl) => {
|
||||
prepareDirTree(childControl, dirTree, dirSortOrders);
|
||||
});
|
||||
}
|
||||
|
||||
return { dirTree, dirSortOrders };
|
||||
}
|
||||
|
||||
const roadmap = require(path.join(__dirname, `../public/jsons/roadmaps/${roadmapId}`));
|
||||
const controls = roadmap.mockup.controls.control;
|
||||
|
||||
// Prepare the dir tree that we will be creating and also calculate the sort orders
|
||||
const dirTree = {};
|
||||
const dirSortOrders = {};
|
||||
|
||||
controls.forEach((control) => {
|
||||
prepareDirTree(control, dirTree, dirSortOrders);
|
||||
});
|
||||
|
||||
const openai = new OpenAIApi(configuration);
|
||||
/**
|
||||
* @param parentDir Parent directory in which directory is to be created
|
||||
* @param dirTree Nested dir tree to be created
|
||||
* @param sortOrders Mapping from groupName to sort order
|
||||
* @param filePaths The mapping from groupName to file path
|
||||
*/
|
||||
function createDirTree(parentDir, dirTree, sortOrders, filePaths = {}) {
|
||||
const childrenDirNames = Object.keys(dirTree);
|
||||
const hasChildren = childrenDirNames.length !== 0;
|
||||
|
||||
function getFilesInFolder(folderPath, fileList = {}) {
|
||||
const files = fs.readdirSync(folderPath);
|
||||
// @todo write test for this, yolo for now
|
||||
const groupName = parentDir
|
||||
.replace(roadmapContentDirPath, '') // Remove base dir path
|
||||
.replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
|
||||
.replace(/(^\d+?-)/g, '') // Remove sorting information
|
||||
.replaceAll('/', ':') // Replace slashes with `:`
|
||||
.replace(/:\d+-/, ':');
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(folderPath, file);
|
||||
const stats = fs.statSync(filePath);
|
||||
const humanizedGroupName = groupName
|
||||
.split(':')
|
||||
.pop()
|
||||
?.replaceAll('-', ' ')
|
||||
.replace(/^\w/, ($0) => $0.toUpperCase());
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
getFilesInFolder(filePath, fileList);
|
||||
} else if (stats.isFile()) {
|
||||
const fileUrl = filePath
|
||||
.replace(ROADMAP_CONTENT_DIR, '') // Remove the content folder
|
||||
.replace(/\/\d+-/g, '/') // Remove ordering info `/101-ecosystem`
|
||||
.replace(/\/index\.md$/, '') // Make the `/index.md` to become the parent folder only
|
||||
.replace(/\.md$/, ''); // Remove `.md` from the end of file
|
||||
const sortOrder = sortOrders[groupName] || '';
|
||||
|
||||
fileList[fileUrl] = filePath;
|
||||
}
|
||||
});
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
function writeTopicContent(currTopicUrl) {
|
||||
const [parentTopic, childTopic] = currTopicUrl
|
||||
.replace(/^\d+-/g, '/')
|
||||
.replace(/:/g, '/')
|
||||
.replace(/^\//, '')
|
||||
.split('/')
|
||||
.slice(-2)
|
||||
.map((topic) => topic.replace(/-/g, ' '));
|
||||
|
||||
const roadmapTitle = roadmapId.replace(/-/g, ' ');
|
||||
|
||||
let prompt = `I am reading a guide about "${roadmapTitle}". I am on the topic "${parentTopic}". I want to know more about "${childTopic}". Write me a brief summary for that topic. Content should be in markdown. Behave as if you are the author of the guide.`;
|
||||
if (!childTopic) {
|
||||
prompt = `I am reading a guide about "${roadmapTitle}". I am on the topic "${parentTopic}". I want to know more about "${parentTopic}". Write me a brief summary for that topic. Content should be in markdown. Behave as if you are the author of the guide.`;
|
||||
// Attach sorting information to dirname
|
||||
// e.g. /roadmaps/100-frontend/content/internet
|
||||
// ———> /roadmaps/100-frontend/content/103-internet
|
||||
if (sortOrder) {
|
||||
parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
|
||||
}
|
||||
|
||||
console.log(`Genearting '${childTopic || parentTopic}'...`);
|
||||
// If no children, create a file for this under the parent directory
|
||||
if (!hasChildren) {
|
||||
let fileName = `${parentDir}.md`;
|
||||
fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
openai
|
||||
.createChatCompletion({
|
||||
model: 'gpt-4',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then((response) => {
|
||||
const article = response.data.choices[0].message.content;
|
||||
|
||||
resolve(article);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const topicUrlToPathMapping = getFilesInFolder(ROADMAP_CONTENT_DIR);
|
||||
|
||||
const roadmapJson = require(path.join(ROADMAP_JSON_DIR, `${roadmapId}.json`));
|
||||
const groups = roadmapJson?.mockup?.controls?.control?.filter(
|
||||
(control) =>
|
||||
control.typeID === '__group__' &&
|
||||
!control.properties?.controlName?.startsWith('ext_link')
|
||||
);
|
||||
|
||||
if (!OPEN_AI_API_KEY) {
|
||||
console.log('----------------------------------------');
|
||||
console.log('OPEN_AI_API_KEY not found. Skipping openai api calls...');
|
||||
console.log('----------------------------------------');
|
||||
filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
for (let group of groups) {
|
||||
const topicId = group?.properties?.controlName;
|
||||
const topicTitle = group?.children?.controls?.control?.find(
|
||||
(control) => control?.typeID === 'Label'
|
||||
)?.properties?.text;
|
||||
const currTopicUrl = topicId?.replace(/^\d+-/g, '/')?.replace(/:/g, '/');
|
||||
if (!currTopicUrl) {
|
||||
continue;
|
||||
}
|
||||
// There *are* children, so create the parent as a directory
|
||||
// and create `index.md` as the content file for this
|
||||
fs.mkdirSync(parentDir);
|
||||
|
||||
const contentFilePath = topicUrlToPathMapping[currTopicUrl];
|
||||
let readmeFilePath = path.join(parentDir, 'index.md');
|
||||
fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
|
||||
|
||||
if (!contentFilePath) {
|
||||
console.log(`Missing file for: ${currTopicUrl}`);
|
||||
return;
|
||||
}
|
||||
filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
|
||||
|
||||
const currentFileContent = fs.readFileSync(contentFilePath, 'utf8');
|
||||
const isFileEmpty = currentFileContent.replace(/^#.+/, ``).trim() === '';
|
||||
// For each of the directory names, create a
|
||||
// directory inside the given directory
|
||||
childrenDirNames.forEach((dirName) => {
|
||||
createDirTree(
|
||||
path.join(parentDir, dirName),
|
||||
dirTree[dirName],
|
||||
dirSortOrders,
|
||||
filePaths
|
||||
);
|
||||
});
|
||||
|
||||
if (!isFileEmpty) {
|
||||
console.log(`Ignoring ${topicId}. Not empty.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let newFileContent = `# ${topicTitle}`;
|
||||
|
||||
if (!OPEN_AI_API_KEY) {
|
||||
console.log(`Writing ${topicId}..`);
|
||||
fs.writeFileSync(contentFilePath, newFileContent, 'utf8');
|
||||
continue;
|
||||
}
|
||||
|
||||
const topicContent = await writeTopicContent(currTopicUrl);
|
||||
newFileContent += `\n\n${topicContent}`;
|
||||
|
||||
console.log(`Writing ${topicId}..`);
|
||||
fs.writeFileSync(contentFilePath, newFileContent, 'utf8');
|
||||
|
||||
// console.log(currentFileContent);
|
||||
// console.log(currTopicUrl);
|
||||
// console.log(topicTitle);
|
||||
// console.log(topicUrlToPathMapping[currTopicUrl]);
|
||||
}
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
run()
|
||||
.then(() => {
|
||||
console.log('Done');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
// Create directories and get back the paths for created directories
|
||||
createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
|
||||
console.log('Created roadmap content directory structure');
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CONTENT_DIR = path.join(__dirname, '../content');
|
||||
// Directory containing the roadmaps
|
||||
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
|
||||
const roadmapId = process.argv[2];
|
||||
|
||||
const allowedRoadmapIds = fs.readdirSync(ROADMAP_CONTENT_DIR);
|
||||
if (!roadmapId) {
|
||||
console.error('roadmapId is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!allowedRoadmapIds.includes(roadmapId)) {
|
||||
console.error(`Invalid roadmap key ${roadmapId}`);
|
||||
console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Directory holding the roadmap content files
|
||||
const roadmapDirName = fs
|
||||
.readdirSync(ROADMAP_CONTENT_DIR)
|
||||
.find((dirName) => dirName.replace(/\d+-/, '') === roadmapId);
|
||||
|
||||
if (!roadmapDirName) {
|
||||
console.error('Roadmap directory not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
|
||||
const roadmapContentDirPath = path.join(
|
||||
ROADMAP_CONTENT_DIR,
|
||||
roadmapDirName,
|
||||
'content'
|
||||
);
|
||||
|
||||
// If roadmap content already exists do not proceed as it would override the files
|
||||
if (fs.existsSync(roadmapContentDirPath)) {
|
||||
console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function prepareDirTree(control, dirTree, dirSortOrders) {
|
||||
// Directories are only created for groups
|
||||
if (control.typeID !== '__group__') {
|
||||
return;
|
||||
}
|
||||
|
||||
// e.g. 104-testing-your-apps:other-options
|
||||
const controlName = control?.properties?.controlName || '';
|
||||
// e.g. 104
|
||||
const sortOrder = controlName.match(/^\d+/)?.[0];
|
||||
|
||||
// No directory for a group without control name
|
||||
if (!controlName || !sortOrder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// e.g. testing-your-apps:other-options
|
||||
const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '');
|
||||
// e.g. ['testing-your-apps', 'other-options']
|
||||
const dirParts = controlNameWithoutSortOrder.split(':');
|
||||
|
||||
// Nest the dir path in the dirTree
|
||||
let currDirTree = dirTree;
|
||||
dirParts.forEach((dirPart) => {
|
||||
currDirTree[dirPart] = currDirTree[dirPart] || {};
|
||||
currDirTree = currDirTree[dirPart];
|
||||
});
|
||||
|
||||
dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);
|
||||
|
||||
const childrenControls = control.children.controls.control;
|
||||
// No more children
|
||||
if (childrenControls.length) {
|
||||
childrenControls.forEach((childControl) => {
|
||||
prepareDirTree(childControl, dirTree, dirSortOrders);
|
||||
});
|
||||
}
|
||||
|
||||
return { dirTree, dirSortOrders };
|
||||
}
|
||||
|
||||
const roadmap = require(path.join(
|
||||
__dirname,
|
||||
`../public/jsons/roadmaps/${roadmapId}`
|
||||
));
|
||||
const controls = roadmap.mockup.controls.control;
|
||||
|
||||
// Prepare the dir tree that we will be creating and also calculate the sort orders
|
||||
const dirTree = {};
|
||||
const dirSortOrders = {};
|
||||
|
||||
controls.forEach((control) => {
|
||||
prepareDirTree(control, dirTree, dirSortOrders);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param parentDir Parent directory in which directory is to be created
|
||||
* @param dirTree Nested dir tree to be created
|
||||
* @param sortOrders Mapping from groupName to sort order
|
||||
* @param filePaths The mapping from groupName to file path
|
||||
*/
|
||||
function createDirTree(parentDir, dirTree, sortOrders, filePaths = {}) {
|
||||
const childrenDirNames = Object.keys(dirTree);
|
||||
const hasChildren = childrenDirNames.length !== 0;
|
||||
|
||||
// @todo write test for this, yolo for now
|
||||
const groupName = parentDir
|
||||
.replace(roadmapContentDirPath, '') // Remove base dir path
|
||||
.replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
|
||||
.replace(/(^\d+?-)/g, '') // Remove sorting information
|
||||
.replaceAll('/', ':') // Replace slashes with `:`
|
||||
.replace(/:\d+-/, ':');
|
||||
|
||||
const humanizedGroupName = groupName
|
||||
.split(':')
|
||||
.pop()
|
||||
?.replaceAll('-', ' ')
|
||||
.replace(/^\w/, ($0) => $0.toUpperCase());
|
||||
|
||||
const sortOrder = sortOrders[groupName] || '';
|
||||
|
||||
// Attach sorting information to dirname
|
||||
// e.g. /roadmaps/100-frontend/content/internet
|
||||
// ———> /roadmaps/100-frontend/content/103-internet
|
||||
if (sortOrder) {
|
||||
parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
|
||||
}
|
||||
|
||||
// If no children, create a file for this under the parent directory
|
||||
if (!hasChildren) {
|
||||
let fileName = `${parentDir}.md`;
|
||||
fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
|
||||
|
||||
filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
// There *are* children, so create the parent as a directory
|
||||
// and create `index.md` as the content file for this
|
||||
fs.mkdirSync(parentDir);
|
||||
|
||||
let readmeFilePath = path.join(parentDir, 'index.md');
|
||||
fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
|
||||
|
||||
filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
|
||||
|
||||
// For each of the directory names, create a
|
||||
// directory inside the given directory
|
||||
childrenDirNames.forEach((dirName) => {
|
||||
createDirTree(
|
||||
path.join(parentDir, dirName),
|
||||
dirTree[dirName],
|
||||
dirSortOrders,
|
||||
filePaths
|
||||
);
|
||||
});
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
// Create directories and get back the paths for created directories
|
||||
createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
|
||||
console.log('Created roadmap content directory structure');
|
||||
@@ -6,7 +6,7 @@ if (!roadmapId) {
|
||||
console.error('Error: roadmapId is required');
|
||||
}
|
||||
|
||||
const fullPath = path.join(__dirname, `../src/data/roadmaps/${roadmapId}`);
|
||||
const fullPath = path.join(__dirname, `../src/roadmaps/${roadmapId}`);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
console.error(`Error: path not found: ${fullPath}!`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const apiKey = process.env.SPONSOR_SHEET_API_KEY;
|
||||
const sheetId = process.env.SPONSOR_SHEET_ID;
|
||||
|
||||
if (!apiKey || !sheetId) {
|
||||
console.error('Missing API key or sheet ID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sheetRange = 'A3:I1001';
|
||||
const sheetUrl = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${sheetRange}?key=${apiKey}`;
|
||||
|
||||
function removeAllSponsors(baseContentDir) {
|
||||
console.log('------------------------');
|
||||
console.log('Removing sponsors from: ', baseContentDir);
|
||||
console.log('------------------------');
|
||||
const dataDirPath = path.join(__dirname, '../src/data');
|
||||
const contentDirPath = path.join(dataDirPath, baseContentDir);
|
||||
|
||||
const contentDir = fs.readdirSync(contentDirPath);
|
||||
contentDir.forEach((content) => {
|
||||
console.log('Removing sponsor from: ', content);
|
||||
|
||||
const pageFilePath = path.join(contentDirPath, content, `${content}.md`);
|
||||
const pageFileContent = fs.readFileSync(pageFilePath, 'utf8');
|
||||
|
||||
const frontMatterRegex = /---\n([\s\S]*?)\n---/;
|
||||
|
||||
const existingFrontmatter = pageFileContent.match(frontMatterRegex)[1];
|
||||
const contentWithoutFrontmatter = pageFileContent
|
||||
.replace(frontMatterRegex, ``)
|
||||
.trim();
|
||||
|
||||
let frontmatterObj = yaml.load(existingFrontmatter);
|
||||
delete frontmatterObj.sponsor;
|
||||
|
||||
const newFrontmatter = yaml.dump(frontmatterObj, {
|
||||
lineWidth: 10000,
|
||||
forceQuotes: true,
|
||||
quotingType: "'",
|
||||
});
|
||||
const newContent = `---\n${newFrontmatter}---\n${contentWithoutFrontmatter}`;
|
||||
|
||||
fs.writeFileSync(pageFilePath, newContent, 'utf8');
|
||||
});
|
||||
}
|
||||
|
||||
function addPageSponsor({
|
||||
pageUrl,
|
||||
company,
|
||||
redirectUrl,
|
||||
imageUrl,
|
||||
adTitle,
|
||||
adDescription,
|
||||
}) {
|
||||
const urlPart = pageUrl
|
||||
.replace('https://roadmap.sh/', '')
|
||||
.replace(/\?.+?$/, '');
|
||||
|
||||
const parentDir = urlPart.startsWith('best-practices/')
|
||||
? 'best-practices'
|
||||
: 'roadmaps';
|
||||
const pageId = urlPart.replace(`${parentDir}/`, '');
|
||||
|
||||
const pageFilePath = path.join(
|
||||
__dirname,
|
||||
`../src/data/${parentDir}`,
|
||||
`${pageId}/${pageId}.md`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(pageFilePath)) {
|
||||
console.error(`Page file not found: ${pageFilePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Updating page: ${urlPart}`);
|
||||
const pageFileContent = fs.readFileSync(pageFilePath, 'utf8');
|
||||
|
||||
const frontMatterRegex = /---\n([\s\S]*?)\n---/;
|
||||
|
||||
const existingFrontmatter = pageFileContent.match(frontMatterRegex)[1];
|
||||
const contentWithoutFrontmatter = pageFileContent
|
||||
.replace(frontMatterRegex, ``)
|
||||
.trim();
|
||||
|
||||
let frontmatterObj = yaml.load(existingFrontmatter);
|
||||
delete frontmatterObj.sponsor;
|
||||
|
||||
const frontmatterValues = Object.entries(frontmatterObj);
|
||||
const roadmapLabel = frontmatterObj.briefTitle;
|
||||
|
||||
// Insert sponsor data at 10 index i.e. after
|
||||
// roadmap dimensions in the frontmatter
|
||||
frontmatterValues.splice(10, 0, [
|
||||
'sponsor',
|
||||
{
|
||||
url: redirectUrl,
|
||||
title: adTitle,
|
||||
imageUrl,
|
||||
description: adDescription,
|
||||
event: {
|
||||
category: 'SponsorClick',
|
||||
action: `${company} Redirect`,
|
||||
label: `${roadmapLabel} / ${company} Link`,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
frontmatterObj = Object.fromEntries(frontmatterValues);
|
||||
|
||||
const newFrontmatter = yaml.dump(frontmatterObj, {
|
||||
lineWidth: 10000,
|
||||
forceQuotes: true,
|
||||
quotingType: "'",
|
||||
});
|
||||
const newContent = `---\n${newFrontmatter}---\n\n${contentWithoutFrontmatter}`;
|
||||
|
||||
fs.writeFileSync(pageFilePath, newContent, 'utf8');
|
||||
}
|
||||
|
||||
// Remove sponsors from all roadmaps
|
||||
removeAllSponsors('roadmaps');
|
||||
removeAllSponsors('best-practices');
|
||||
|
||||
console.log('------------------------');
|
||||
console.log('Adding sponsors');
|
||||
console.log('------------------------');
|
||||
fetch(sheetUrl)
|
||||
.then((res) => res.json())
|
||||
.then((rawData) => {
|
||||
const rows = rawData.values;
|
||||
|
||||
rows.map((row) => {
|
||||
// prettier-ignore
|
||||
const [
|
||||
pageUrl,
|
||||
company,
|
||||
redirectUrl,
|
||||
imageUrl,
|
||||
adTitle,
|
||||
adDescription,
|
||||
startDate,
|
||||
endDate,
|
||||
isActive,
|
||||
] = row;
|
||||
|
||||
const isConfiguredActive = isActive?.toLowerCase() === 'yes';
|
||||
const currentDate = new Date();
|
||||
const isDateInRange =
|
||||
currentDate >= new Date(startDate) && currentDate <= new Date(endDate);
|
||||
|
||||
if (!isConfiguredActive || !isDateInRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
addPageSponsor({
|
||||
pageUrl,
|
||||
company,
|
||||
redirectUrl,
|
||||
imageUrl,
|
||||
adTitle,
|
||||
adDescription,
|
||||
startDate,
|
||||
endDate,
|
||||
isActive,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,21 +14,21 @@ appearance, race, religion, or sexual identity and orientation.
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
@@ -23,7 +23,7 @@ For the existing roadmaps, please follow the details listed for the nature of co
|
||||
|
||||
## Adding Content
|
||||
|
||||
Find [the content directory inside the relevant roadmap](https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/roadmaps). Please keep the following guidelines in mind when submitting content:
|
||||
Find [the content directory inside the relevant roadmap](https://github.com/kamranahmedse/developer-roadmap/tree/master/src/roadmaps). Please keep the following guidelines in mind when submitting content:
|
||||
|
||||
- Content must be in English.
|
||||
- Put a brief description about the topic on top of the file and the a list of links below with each link having title of the URL.
|
||||
|
||||
28
package.json
@@ -8,37 +8,33 @@
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"format": "prettier --write .",
|
||||
"astro": "astro",
|
||||
"deploy": "NODE_DEBUG=gh-pages gh-pages -d dist -t",
|
||||
"compress:jsons": "node bin/compress-jsons.cjs",
|
||||
"upgrade": "ncu -u",
|
||||
"roadmap-links": "node bin/roadmap-links.cjs",
|
||||
"roadmap-dirs": "node bin/roadmap-dirs.cjs",
|
||||
"roadmap-content": "node bin/roadmap-content.cjs",
|
||||
"best-practice-dirs": "node bin/best-practice-dirs.cjs",
|
||||
"best-practice-content": "node bin/best-practice-content.cjs",
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/sitemap": "^1.2.1",
|
||||
"@astrojs/tailwind": "^3.1.1",
|
||||
"astro": "^2.1.7",
|
||||
"astro-compress": "^1.1.35",
|
||||
"node-html-parser": "^6.1.5",
|
||||
"npm-check-updates": "^16.8.0",
|
||||
"@astrojs/sitemap": "^1.0.0",
|
||||
"@astrojs/tailwind": "^2.1.3",
|
||||
"astro": "^1.9.2",
|
||||
"astro-compress": "^1.1.28",
|
||||
"node-html-parser": "^6.1.4",
|
||||
"npm-check-updates": "^16.6.2",
|
||||
"rehype-external-links": "^2.0.1",
|
||||
"roadmap-renderer": "^1.0.4",
|
||||
"tailwindcss": "^3.2.7"
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.32.1",
|
||||
"@playwright/test": "^1.29.2",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"gh-pages": "^5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-to-pretty-yaml": "^1.2.2",
|
||||
"markdown-it": "^13.0.1",
|
||||
"openai": "^3.2.1",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier-plugin-astro": "^0.8.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.6"
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-astro": "^0.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ const config: PlaywrightTestConfig = {
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
url: 'http://localhost:3000',
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
};
|
||||
|
||||
5879
pnpm-lock.yaml
generated
|
Before Width: | Height: | Size: 505 KiB |
|
Before Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 542 KiB |
|
Before Width: | Height: | Size: 528 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 696 KiB |
11
readme.md
@@ -38,7 +38,6 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [Software Architect Roadmap](https://roadmap.sh/software-architect)
|
||||
- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture)
|
||||
- [JavaScript Roadmap](https://roadmap.sh/javascript)
|
||||
- [TypeScript Roadmap](https://roadmap.sh/typescript)
|
||||
- [React Roadmap](https://roadmap.sh/react)
|
||||
- [Vue Roadmap](https://roadmap.sh/vue)
|
||||
- [Angular Roadmap](https://roadmap.sh/angular)
|
||||
@@ -55,16 +54,6 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [Blockchain Roadmap](https://roadmap.sh/blockchain)
|
||||
- [ASP.NET Core Roadmap](https://roadmap.sh/aspnet-core)
|
||||
- [System Design Roadmap](https://roadmap.sh/system-design)
|
||||
- [Kubernetes Roadmap](https://roadmap.sh/kubernetes)
|
||||
- [Cyber Security Roadmap](https://roadmap.sh/cyber-security)
|
||||
- [MongoDB Roadmap](https://roadmap.sh/mongodb)
|
||||
- [UX Design Roadmap](https://roadmap.sh/ux-design)
|
||||
|
||||
We have also added a new form of visual content covering best practices:
|
||||
|
||||
- [Frontend Performance Best Practices](https://roadmap.sh/best-practices/frontend-performance)
|
||||
- [API Security Best Practices](https://roadmap.sh/best-practices/api-security)
|
||||
- [AWS Best Practices](https://roadmap.sh/best-practices/aws)
|
||||
|
||||

|
||||
|
||||
|
||||
23
sitemap.mjs
@@ -2,21 +2,20 @@ import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
async function getRoadmapIds() {
|
||||
return fs.readdir(path.join(process.cwd(), 'src/data/roadmaps'));
|
||||
return fs.readdir(path.join(process.cwd(), 'src/roadmaps'));
|
||||
}
|
||||
|
||||
async function getBestPracticesIds() {
|
||||
return fs.readdir(path.join(process.cwd(), 'src/data/best-practices'));
|
||||
return fs.readdir(path.join(process.cwd(), 'src/best-practices'));
|
||||
}
|
||||
|
||||
export function shouldIndexPage(pageUrl) {
|
||||
export function shouldIndexPage(page) {
|
||||
return ![
|
||||
'https://roadmap.sh/404',
|
||||
'https://roadmap.sh/terms',
|
||||
'https://roadmap.sh/privacy',
|
||||
'https://roadmap.sh/pdfs',
|
||||
'https://roadmap.sh/g',
|
||||
].includes(pageUrl);
|
||||
].includes(page);
|
||||
}
|
||||
|
||||
export async function serializeSitemap(item) {
|
||||
@@ -27,13 +26,8 @@ export async function serializeSitemap(item) {
|
||||
'https://roadmap.sh/best-practices',
|
||||
'https://roadmap.sh/guides',
|
||||
'https://roadmap.sh/videos',
|
||||
...(await getRoadmapIds()).flatMap((id) => [
|
||||
`https://roadmap.sh/${id}`,
|
||||
`https://roadmap.sh/${id}/topics`,
|
||||
]),
|
||||
...(await getBestPracticesIds()).map(
|
||||
(id) => `https://roadmap.sh/best-practices/${id}`
|
||||
),
|
||||
...(await getRoadmapIds()).flatMap((id) => [`https://roadmap.sh/${id}`, `https://roadmap.sh/${id}/topics`]),
|
||||
...(await getBestPracticesIds()).map((id) => `https://roadmap.sh/best-practices/${id}`),
|
||||
];
|
||||
|
||||
// Roadmaps and other high priority pages
|
||||
@@ -49,10 +43,7 @@ export async function serializeSitemap(item) {
|
||||
}
|
||||
|
||||
// Guide and video pages
|
||||
if (
|
||||
item.url.startsWith('https://roadmap.sh/guides') ||
|
||||
item.url.startsWith('https://roadmap.sh/videos')
|
||||
) {
|
||||
if (item.url.startsWith('https://roadmap.sh/guides') || item.url.startsWith('https://roadmap.sh/videos')) {
|
||||
return {
|
||||
...item,
|
||||
// @ts-ignore
|
||||
|
||||
@@ -9,4 +9,4 @@ Your CSS should be organized, using a CSS preprocessor can help you with that. S
|
||||
- [TestMyCSS | Optimize and Check CSS Performance](http://www.testmycss.com/)
|
||||
- [CSS Stats](https://cssstats.com/)
|
||||
- [macbre/analyze-css: CSS selectors complexity and performance analyzer](https://github.com/macbre/analyze-css)
|
||||
- [Project Wallace](https://www.projectwallace.com/) is like CSS Stats but stores stats over time so you can track your changes
|
||||
- [Project Wallace](https://www.projectwallace.com/) is like CSS Stats but stores stats over time so you can track your changes
|
||||
@@ -10,4 +10,4 @@ Use the Timeline tool in the Chrome Developer Tool to evaluate scripts events an
|
||||
- [JavaScript Profiling With The Chrome Developer Tools](https://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/)
|
||||
- [How to Record Heap Snapshots | Tools for Web Developers](https://developers.google.com/web/tools/chrome-devtools/memory-problems/heap-snapshots)
|
||||
- [Chapter 22 - Profiling the Frontend - Blackfire](https://blackfire.io/docs/book/22-frontend-profiling)
|
||||
- [30 Tips To Improve Javascript Performance](http://www.monitis.com/blog/30-tips-to-improve-javascript-performance/)
|
||||
- [30 Tips To Improve Javascript Performance](http://www.monitis.com/blog/30-tips-to-improve-javascript-performance/)
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
404 request can slow down the performance of your website and negatively impact the user experience. Additionally, they can also cause search engines to crawl and index non-existent pages, which can negatively impact your search engine rankings. To avoid 404 requests, ensure that all links on your website are valid and that any broken links are fixed promptly.
|
||||
|
||||
- [How to avoid Bad Requests?](https://varvy.com/pagespeed/avoid-bad-requests.html)
|
||||
- [How to avoid Bad Requests?](https://varvy.com/pagespeed/avoid-bad-requests.html)
|
||||
@@ -17,4 +17,4 @@ Instead of using Base64 encoded images, it is generally recommended to use binar
|
||||
- [Base64 Encoding & Performance, Part 1 and 2 by Harry Roberts](https://csswizardry.com/2017/02/base64-encoding-and-performance/)
|
||||
- [A closer look at Base64 image performance – The Page Not Found Blog](http://www.andygup.net/a-closer-look-at-base64-image-performance/)
|
||||
- [When to base64 encode images (and when not to) | David Calhoun](https://www.davidbcalhoun.com/2011/when-to-base64-encode-images-and-when-not-to/)
|
||||
- [Base64 encoding images for faster pages | Performance and seo factors](https://varvy.com/pagespeed/base64-images.html)
|
||||
- [Base64 encoding images for faster pages | Performance and seo factors](https://varvy.com/pagespeed/base64-images.html)
|
||||
@@ -6,4 +6,4 @@ One of the first reason it's because it's a good practice to separate content fr
|
||||
|
||||
Always use external stylesheets or embed CSS in your `<head>` (and follow the others CSS performance rules)
|
||||
|
||||
- [Observe CSS Best Practices: Avoid CSS Inline Styles](https://www.lifewire.com/avoid-inline-styles-for-css-3466846)
|
||||
- [Observe CSS Best Practices: Avoid CSS Inline Styles](https://www.lifewire.com/avoid-inline-styles-for-css-3466846)
|
||||
@@ -6,4 +6,4 @@ Placing JavaScript embedded code directly in your `<body>` can slow down your pa
|
||||
|
||||
Ensure that all your files are loaded using `async` or `defer` and decide wisely the code that you will need to inject in your `<head>`.
|
||||
|
||||
- [11 Tips to Optimize JavaScript and Improve Website Loading Speeds](https://www.upwork.com/hiring/development/11-tips-to-optimize-javascript-and-improve-website-loading-speeds/)
|
||||
- [11 Tips to Optimize JavaScript and Improve Website Loading Speeds](https://www.upwork.com/hiring/development/11-tips-to-optimize-javascript-and-improve-website-loading-speeds/)
|
||||
@@ -9,4 +9,4 @@ Always compare and choose the best and lighter library for your needs. You can a
|
||||
- [ai/size-limit: Prevent JS libraries bloat](https://github.com/ai/size-limit)
|
||||
- [webpack-bundle-analyzer - npm](https://www.npmjs.com/package/webpack-bundle-analyzer)
|
||||
- [js-dependency-viewer - npm](https://www.npmjs.com/package/js-dependency-viewer)
|
||||
- [Size Limit: Make the Web lighter](https://evilmartians.com/chronicles/size-limit-make-the-web-lighter)
|
||||
- [Size Limit: Make the Web lighter](https://evilmartians.com/chronicles/size-limit-make-the-web-lighter)
|
||||
@@ -9,4 +9,4 @@ Use [Lighthouse](https://developers.google.com/web/tools/lighthouse/) to identif
|
||||
- [Serve Images in Next-Gen Formats](https://developers.google.com/web/tools/lighthouse/audits/webp)
|
||||
- [What Is the Right Image Format for Your Website?](https://www.sitepoint.com/what-is-the-right-image-format-for-your-website/)
|
||||
- [PNG8 - The Clear Winner](https://www.sitepoint.com/png8-the-clear-winner/)
|
||||
- [8-bit vs 16-bit - What Color Depth You Should Use And Why It Matters](https://www.diyphotography.net/8-bit-vs-16-bit-color-depth-use-matters/)
|
||||
- [8-bit vs 16-bit - What Color Depth You Should Use And Why It Matters](https://www.diyphotography.net/8-bit-vs-16-bit-color-depth-use-matters/)
|
||||
@@ -12,4 +12,4 @@ Some of the benefits of using Chrome DevTools include:
|
||||
|
||||
Chrome DevTools are a powerful and essential tool for web developers, as it helps to improve debugging, testing, and optimization of the web application.
|
||||
|
||||
- [Chrome DevTools Docs](https://developer.chrome.com/docs/devtools/)
|
||||
- [Chrome DevTools Docs](https://developer.chrome.com/docs/devtools/)
|
||||
@@ -16,4 +16,4 @@ Optimized images load faster in your browser and consume less data.
|
||||
- [Compressor.io](https://compressor.io/compress)
|
||||
- [Cloudinary - Image Analysis Tool](https://webspeedtest.cloudinary.com)
|
||||
- [ImageEngine - Image Webpage Loading Test](https://demo.imgeng.in)
|
||||
- [SVGOMG - Optimize SVG vector graphics files](https://jakearchibald.github.io/svgomg/)
|
||||
- [SVGOMG - Optimize SVG vector graphics files](https://jakearchibald.github.io/svgomg/)
|
||||
@@ -8,4 +8,4 @@ If you are still using HTTP/1, you may need to still concatenate your files, it'
|
||||
- Ensure, of course, that concatenation does not break your project
|
||||
|
||||
- [HTTP: Optimizing Application Delivery - High Performance Browser Networking (O'Reilly)](https://hpbn.co/optimizing-application-delivery/#optimizing-for-http2)
|
||||
- [Performance Best Practices in the HTTP/2 Era](https://deliciousbrains.com/performance-best-practices-http2/)
|
||||
- [Performance Best Practices in the HTTP/2 Era](https://deliciousbrains.com/performance-best-practices-http2/)
|
||||
@@ -8,4 +8,4 @@ Cookies are exchanged in the HTTP headers between web servers and browsers. It's
|
||||
- [Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
|
||||
- [Browser Cookie Limits](http://browsercookielimits.squawky.net/)
|
||||
- [Website Performance: Cookies Don't Taste So Good - Monitis Blog](http://www.monitis.com/blog/website-performance-cookies-dont-taste-so-good/)
|
||||
- [Google's Web Performance Best Practices #3: Minimize Request Overhead - GlobalDots Blog](https://www.globaldots.com/googles-web-performance-best-practices-3-minimize-request-overhead/)
|
||||
- [Google's Web Performance Best Practices #3: Minimize Request Overhead - GlobalDots Blog](https://www.globaldots.com/googles-web-performance-best-practices-3-minimize-request-overhead/)
|
||||
@@ -4,4 +4,4 @@ Use a compression method such as Gzip or Brotli to reduce the size of your JavaS
|
||||
|
||||
- [Check GZIP compression](https://checkgzipcompression.com/)
|
||||
- [Check Brotli Compression](https://tools.keycdn.com/brotli-test)
|
||||
- [Can I use... Brotli](https://caniuse.com/#feat=brotli)
|
||||
- [Can I use... Brotli](https://caniuse.com/#feat=brotli)
|
||||
@@ -15,4 +15,4 @@ These guides are intended to help you optimize your application for performance
|
||||
|
||||
## Vue
|
||||
|
||||
- [Vue - Useful Links|Style Guide and Performance](https://learn-vuejs.github.io/vue-patterns/useful-links/)
|
||||
- [Vue - Useful Links|Style Guide and Performance](https://learn-vuejs.github.io/vue-patterns/useful-links/)
|
||||
1
src/best-practices/frontend-performance/content/index.md
Normal file
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -10,4 +10,4 @@ Generate the CSS critical with online tools or using a plugin like the one that
|
||||
- [Critical by Addy Osmani on GitHub](https://github.com/addyosmani/critical) automates this.
|
||||
- [Inlining critical CSS for better web performance | Go Make Things](https://gomakethings.com/inlining-critical-css-for-better-web-performance/)
|
||||
- [Critical Path CSS Generator - Prioritize above the fold content :: SiteLocity](https://www.sitelocity.com/critical-path-css-generator)
|
||||
- [Reduce the size of the above-the-fold content](https://developers.google.com/speed/docs/insights/PrioritizeVisibleContent)
|
||||
- [Reduce the size of the above-the-fold content](https://developers.google.com/speed/docs/insights/PrioritizeVisibleContent)
|
||||
@@ -8,4 +8,4 @@ Cookies are exchanged in the HTTP headers between web servers and browsers. It's
|
||||
- [Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
|
||||
- [Browser Cookie Limits](http://browsercookielimits.squawky.net/)
|
||||
- [Website Performance: Cookies Don't Taste So Good - Monitis Blog](http://www.monitis.com/blog/website-performance-cookies-dont-taste-so-good/)
|
||||
- [Google's Web Performance Best Practices #3: Minimize Request Overhead - GlobalDots Blog](https://www.globaldots.com/googles-web-performance-best-practices-3-minimize-request-overhead/)
|
||||
- [Google's Web Performance Best Practices #3: Minimize Request Overhead - GlobalDots Blog](https://www.globaldots.com/googles-web-performance-best-practices-3-minimize-request-overhead/)
|
||||
@@ -6,4 +6,5 @@ Most of the time, new versions come with optimization and security fix. You shou
|
||||
|
||||
If your project use NPM packages, [npm-check](https://www.npmjs.com/package/npm-check) is a pretty interesting library to upgrade / update your libraries. [Greenkeeper](https://greenkeeper.io/) can automatically look for your dependencies and suggest an update every time a new version is out.
|
||||
|
||||
- [Vanilla JavaScript for building powerful web applications](https://plainjs.com/)
|
||||
|
||||
- [Vanilla JavaScript for building powerful web applications](https://plainjs.com/)
|
||||
@@ -5,4 +5,4 @@ Reduce as much as you can the time your browser waits before receiving data.
|
||||
- [What is Waiting (TTFB) in DevTools, and what to do about it](https://scaleyourcode.com/blog/article/27)
|
||||
- [Monitoring your servers with free tools is easy](https://scaleyourcode.com/blog/article/7)
|
||||
- [Time to First Byte (TTFB)](https://varvy.com/pagespeed/ttfb.html)
|
||||
- [Global latency testing tool](https://latency.apex.sh)
|
||||
- [Global latency testing tool](https://latency.apex.sh)
|
||||
@@ -4,4 +4,4 @@ Web fonts are a great way to add style to your website. However, they can also b
|
||||
|
||||
Webfont sizes shouldn't exceed 300kb (all variants included) and are optimized for performance.
|
||||
|
||||
- [Font Bytes - Page Weight](https://httparchive.org/reports/page-weight#bytesFont)
|
||||
- [Font Bytes - Page Weight](https://httparchive.org/reports/page-weight#bytesFont)
|
||||
@@ -11,5 +11,5 @@ It will improve the response time of the current page and then avoid loading unn
|
||||
- [verlok/lazyload: GitHub](https://github.com/verlok/lazyload)
|
||||
- [aFarkas/lazysizes: GitHub](https://github.com/aFarkas/lazysizes/)
|
||||
- [mfranzke/loading-attribute-polyfill: GitHub](https://github.com/mfranzke/loading-attribute-polyfill/)
|
||||
- [Lazy Loading Images and Video | Web Fundamentals | Google Developers](https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/)
|
||||
- [Lazy Loading Images and Video | Web Fundamentals | Google Developers](https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/)
|
||||
- [5 Brilliant Ways to Lazy Load Images For Faster Page Loads - Dynamic Drive Blog](http://blog.dynamicdrive.com/5-brilliant-ways-to-lazy-load-images-for-faster-page-loads/)
|
||||
@@ -9,4 +9,4 @@ You need to add the `rel` attribute with the preload value and add `as="style"`
|
||||
- [loadCSS by filament group](https://github.com/filamentgroup/loadCSS)
|
||||
- [Example of preload CSS using loadCSS](https://gist.github.com/thedaviddias/c24763b82b9991e53928e66a0bafc9bf)
|
||||
- [Preloading content with rel="preload"](https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content)
|
||||
- [Preload: What Is It Good For? — Smashing Magazine](https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/)
|
||||
- [Preload: What Is It Good For? — Smashing Magazine](https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/)
|
||||
@@ -9,4 +9,4 @@ Use tools to minify your files automatically before or during your build or your
|
||||
- [cssnano: A modular minifier based on the PostCSS ecosystem. - cssnano](https://cssnano.co/)
|
||||
- [CSS Minfier](https://goonlinetools.com/css-minifier/)
|
||||
- [@neutrinojs/style-minify - npm](https://www.npmjs.com/package/@neutrinojs/style-minify)
|
||||
- [Online CSS Compressor](http://refresh-sf.com)
|
||||
- [Online CSS Compressor](http://refresh-sf.com)
|
||||
@@ -1,5 +1,4 @@
|
||||
# Minify HTML
|
||||
|
||||
> The HTML code is minified, comments, white spaces and new lines are removed from production files.
|
||||
|
||||
Removing all unnecessary spaces, comments and attributes will reduce the size of your HTML and speed up your site's page load times and obviously lighten the download for your user.
|
||||
@@ -8,4 +7,4 @@ Most of the frameworks have plugins to facilitate the minification of the webpag
|
||||
|
||||
- [HTML minifier | Minify Code](http://minifycode.com/html-minifier/)
|
||||
- [Online HTML Compressor](http://refresh-sf.com)
|
||||
- [Experimenting with HTML minifier — Perfection Kills](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype)
|
||||
- [Experimenting with HTML minifier — Perfection Kills](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype)
|
||||
@@ -9,4 +9,4 @@ Use the tools suggested below to minify your files automatically before or durin
|
||||
- [terser - JavaScript parser, mangler and compressor toolkit for ES6+](https://github.com/terser/terser)
|
||||
- [uglify-js - npm](https://www.npmjs.com/package/uglify-js)
|
||||
- [Online JavaScript Compressor](http://refresh-sf.com)
|
||||
- [Short read: How is HTTP/2 different? Should we still minify and concatenate?](https://scaleyourcode.com/blog/article/28)
|
||||
- [Short read: How is HTTP/2 different? Should we still minify and concatenate?](https://scaleyourcode.com/blog/article/28)
|
||||
@@ -3,4 +3,4 @@
|
||||
> Always ensure that every file requested are essential for your website or application.
|
||||
|
||||
- [Combine external CSS](https://varvy.com/pagespeed/combine-external-css.html)
|
||||
- [Combine external JavaScript](https://varvy.com/pagespeed/combine-external-javascript.html)
|
||||
- [Combine external JavaScript](https://varvy.com/pagespeed/combine-external-javascript.html)
|
||||
@@ -1,3 +1,3 @@
|
||||
# Avoid iframes
|
||||
|
||||
Use iframes only if you don't have any other technical possibility. Try to avoid iframes as much as you can. Iframes are not only bad for performance, but also for accessibility and usability. Iframes are also not indexed by search engines.
|
||||
Use iframes only if you don't have any other technical possibility. Try to avoid iframes as much as you can. Iframes are not only bad for performance, but also for accessibility and usability. Iframes are also not indexed by search engines.
|
||||
@@ -1,5 +1,4 @@
|
||||
# Page Load Time
|
||||
|
||||
> Reduce as much as possible your page load times to quickly deliver your content to your users.
|
||||
|
||||
Faster your website or app is, less you have probability of bounce increases, in other terms you have less chances to lose your user or future client. Enough researches on the subject prove that point.
|
||||
@@ -8,4 +7,4 @@ Use online tools like [Page Speed Insights](https://developers.google.com/speed/
|
||||
|
||||
- [Compare your mobile site speed](https://www.thinkwithgoogle.com/feature/mobile/)
|
||||
- [Test Your Mobile Website Speed and Performance - Think With Google](https://testmysite.thinkwithgoogle.com/intl/en-us)
|
||||
- [Average Page Load Times for 2018 - How does yours compare? - MachMetrics Speed Blog](https://www.machmetrics.com/speed-blog/average-page-load-times-websites-2018/)
|
||||
- [Average Page Load Times for 2018 - How does yours compare? - MachMetrics Speed Blog](https://www.machmetrics.com/speed-blog/average-page-load-times-websites-2018/)
|
||||
@@ -1,5 +1,5 @@
|
||||
# Page Speed Insights
|
||||
|
||||
Page Speed Insights is a free tool from Google that analyzes the performance of a web page and provides suggestions for improvements.
|
||||
Page Speed Insights is a free tool from Google that analyzes the performance of a web page and provides suggestions for improvements.
|
||||
|
||||
- [Page Speed Insights](https://pagespeed.web.dev/)
|
||||
- [Page Speed Insights](https://pagespeed.web.dev/)
|
||||
@@ -8,4 +8,4 @@ All the listed best practices in this list will help you to reduce as much as po
|
||||
|
||||
- [Page Weight](https://httparchive.org/reports/page-weight#bytesTotal)
|
||||
- [What Does My Site Cost?](https://whatdoesmysitecost.com/)
|
||||
- [web - Measure full page size in Chrome DevTools - Stack Overflow](https://stackoverflow.com/questions/38239980/measure-full-page-size-in-chrome-devtools)
|
||||
- [web - Measure full page size in Chrome DevTools - Stack Overflow](https://stackoverflow.com/questions/38239980/measure-full-page-size-in-chrome-devtools)
|
||||
@@ -6,4 +6,4 @@ Prefetching allows a browser to silently fetch the necessary resources needed to
|
||||
|
||||
- [What Is Prefetching and Why Use It](https://www.keycdn.com/support/prefetching)
|
||||
- [Prefetching, preloading, prebrowsing](https://css-tricks.com/prefetching-preloading-prebrowsing/)
|
||||
- [What is Preload, Prefetch, and Preconnect](https://www.keycdn.com/blog/resource-hints)
|
||||
- [What is Preload, Prefetch, and Preconnect](https://www.keycdn.com/blog/resource-hints)
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Prefer using vector image rather than bitmap images (when possible).
|
||||
|
||||
Vector images (SVG) tend to be smaller than images and SVG's are responsive and scale perfectly. These images can be animated and modified by CSS.
|
||||
Vector images (SVG) tend to be smaller than images and SVG's are responsive and scale perfectly. These images can be animated and modified by CSS.
|
||||
@@ -3,4 +3,4 @@
|
||||
> Avoid transparent text until the Webfont is loaded
|
||||
|
||||
- [`font-display` for the Masses](https://css-tricks.com/font-display-masses/)
|
||||
- [CSS font-display: The Future of Font Rendering on the Web](https://www.sitepoint.com/css-font-display-future-font-rendering-web/)
|
||||
- [CSS font-display: The Future of Font Rendering on the Web](https://www.sitepoint.com/css-font-display-future-font-rendering-web/)
|
||||
@@ -0,0 +1,90 @@
|
||||
# Recommended Guides
|
||||
|
||||
> Optimize the critical rendering path:
|
||||
|
||||
* [Critical CSS? Not So Fast!](https://csswizardry.com/2022/09/critical-css-not-so-fast/)
|
||||
* [Priority Hints - What Your Browser Doesn’t Know (Yet)](https://www.etsy.com/codeascraft/priority-hints-what-your-browser-doesnt-know-yet)
|
||||
* [Optimizing resource loading with Priority Hints](https://web.dev/priority-hints/)
|
||||
* [Chrome Resource Priorities and Scheduling](https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc/edit?usp=sharing)
|
||||
* [How To Optimize CSS for Peak Site Performance](https://kinsta.com/blog/optimize-css/)
|
||||
* [Eliminate render blocking CSS to improve start render time](https://www.jeffreyknox.dev/blog/eliminate-render-blocking-css-to-improve-start-render-time/)
|
||||
* [Small Bundles, Fast Pages: What To Do With Too Much JavaScript](https://calibreapp.com/blog/bundle-size-optimization)
|
||||
* [How to Eliminate Render-Blocking Resources: a Deep Dive](https://sia.codes/posts/render-blocking-resources/)
|
||||
* [The Critical Request: How to Prioritise Requests to Improve Speed](https://calibreapp.com/blog/critical-request)
|
||||
* [How to Improve CSS Performance](https://calibreapp.com/blog/css-performance)
|
||||
* [The Simplest Way to Load CSS Asynchronously](https://www.filamentgroup.com/lab/load-css-simpler/)
|
||||
* [CSS audit](https://css-tricks.com/a-quick-css-audit-and-general-notes-about-design-systems/)
|
||||
* [Measuring the Critical Rendering Path](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp)
|
||||
* [Inlining or Caching? Both Please!](https://www.filamentgroup.com/lab/inlining-cache.html)
|
||||
* [CSS and Network Performance](https://csswizardry.com/2018/11/css-and-network-performance/)
|
||||
* [Analyzing Critical Rendering Path Performance](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp)
|
||||
* [Front-End Performance Checklist](https://github.com/thedaviddias/Front-End-Performance-Checklist)
|
||||
* [The PRPL Pattern](https://developers.google.com/web/fundamentals/performance/prpl-pattern/)
|
||||
* [Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver](https://www.smashingmagazine.com/2018/01/deferring-lazy-loading-intersection-observer-api/)
|
||||
* [Optimising the front end for the browser](https://hackernoon.com/optimising-the-front-end-for-the-browser-f2f51a29c572)
|
||||
* [Prefer DEFER Over ASYNC](https://calendar.perfplanet.com/2016/prefer-defer-over-async/)
|
||||
* [A comprehensive guide to font loading strategies](https://www.zachleat.com/web/comprehensive-webfonts/)
|
||||
* [Understanding the critical rendering path, rendering pages in 1 second](https://medium.com/@luisvieira_gmr/understanding-the-critical-rendering-path-rendering-pages-in-1-second-735c6e45b47a)
|
||||
* [More Weight Doesn’t Mean More Wait](https://www.filamentgroup.com/lab/weight-wait.html)
|
||||
|
||||
> JavaScript Rendering Performance
|
||||
|
||||
* [Five Data-Loading Patterns To Boost Web Performance](https://www.smashingmagazine.com/2022/09/data-loading-patterns-improve-frontend-performance/)
|
||||
* [Optimize long tasks](https://web.dev/optimize-long-tasks/)
|
||||
* [The impact of removing jQuery on our web performance](https://insidegovuk.blog.gov.uk/2022/08/15/the-impact-of-removing-jquery-on-our-web-performance/)
|
||||
* [Profiling & Optimizing the runtime performance with the DevTools Performance tab](iamtk.co/profiling-and-optimizing-the-runtime-performance-with-the-devtools-performance-tab)
|
||||
* [Don't fight the browser preload scanner](https://web.dev/preload-scanner/)
|
||||
* [The Web Performance impact of jQuery](https://twitter.com/TheRealNooshu/status/1509487050122276864)
|
||||
* [Have Single-Page Apps Ruined the Web? | Transitional Apps](https://www.youtube.com/watch?v=860d8usGC0o)
|
||||
* [Improve how you architect webapps](https://www.patterns.dev/)
|
||||
* [Nuxt SSR Optimizing Tips](https://vueschool.io/articles/vuejs-tutorials/nuxt-ssr-optimizing-tips/, Filip Rakowski
|
||||
* [GPU accelerated JavaScript](https://gpu.rocks/#/)
|
||||
* [Introducing Partytown 🎉: Run Third-Party Scripts From a Web Worker](https://dev.to/adamdbradley/introducing-partytown-run-third-party-scripts-from-a-web-worker-2cnp)
|
||||
* [Astro: Astro is a fresh but familiar approach to building websites. Astro combines decades of proven performance best practices with the DX improvements of the component-oriented era. Use your favorite JavaScript framework and automatically ship the bare-minimum amount of JavaScript—by default.](https://docs.astro.build/getting-started/)
|
||||
* [Minimising Layout and Layout thrashing for 60 FPS](https://www.charistheo.io/blog/2021/09/dom-reflow-and-layout-thrashing/)
|
||||
* [Does shadow DOM improve style performance?](https://nolanlawson.com/2021/08/15/does-shadow-dom-improve-style-performance/)
|
||||
* [Debugging memory leaks - HTTP 203](https://www.youtube.com/watch?v=YDU_3WdfkxA)
|
||||
* [Explore JavaScript Dependencies With Lighthouse Treemap](https://sia.codes/posts/lighthouse-treemap/)
|
||||
* [The real cost of Javascript dependencies (and the state of JS package quality)](https://medium.com/voodoo-engineering/the-real-cost-of-javascript-dependencies-and-the-state-of-js-package-quality-a8dacd74c0ec)
|
||||
* [The State Of Web Workers In 2021](https://www.smashingmagazine.com/2021/06/web-workers-2021/)
|
||||
* [Techniques for developing high-performance animations](https://web.dev/animations/)
|
||||
* [Building a Faster Web Experience with the postTask Scheduler](https://medium.com/airbnb-engineering/building-a-faster-web-experience-with-the-posttask-scheduler-276b83454e91), Callie (Airbnb Engineering & Data Science)
|
||||
* [Don’t attach tooltips to document.body – Learn how the browser works – Debug forced reflow](https://atfzl.com/don-t-attach-tooltips-to-document-body)
|
||||
* [How to Create and Fix Memory Leaks With Chrome DevTools](https://betterprogramming.pub/build-me-an-angular-app-with-memory-leaks-please-36302184e658)
|
||||
* [JavaScript performance beyond bundle size](https://nolanlawson.com/2021/02/23/javascript-performance-beyond-bundle-size/)
|
||||
* [The Import On Interaction Pattern](https://addyosmani.com/blog/import-on-interaction/)
|
||||
* [The “Live DOM” Is Not “Slow”, “Bad”, Or “Wrong”. Web Developers Are.](https://levelup.gitconnected.com/the-live-dom-is-not-slow-bad-or-wrong-web-developers-are-2bf86c3b9e2e)
|
||||
* [Prevent layout shifts with CSS grid stacks](https://www.hsablonniere.com/prevent-layout-shifts-with-css-grid-stacks--qcj5jo/)
|
||||
* [content-visibility: the new CSS property that boosts your rendering performance](https://web.dev/content-visibility/)
|
||||
* [Preact vs React - Updating React at Etsy](https://github.com/mq2thez/blog/blob/main/upgrade-react-etsy/preact-vs-react.md)
|
||||
* [The Cost of Javascript Frameworks](https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/)
|
||||
* [Fixing memory leaks in web applications](https://nolanlawson.com/2020/02/19/fixing-memory-leaks-in-web-applications/)
|
||||
* [How to load polyfills only when needed](https://3perf.com/blog/polyfills/)
|
||||
* [Responsible JavaScript: Part III - Third parties](https://alistapart.com/article/responsible-javascript-part-3/)
|
||||
* [The cost of JavaScript in 2019](https://v8.dev/blog/cost-of-javascript-2019)
|
||||
* [When should you be using Web Workers?](https://dassur.ma/things/when-workers/)
|
||||
* [Responsible Javascript: Part II - Code Bundle](https://alistapart.com/article/responsible-javascript-part-2/)
|
||||
* [Faster script loading with BinaryAST?](https://blog.cloudflare.com/binary-ast/)
|
||||
* [Svelte 3: Rethinking reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity)
|
||||
* [Responsible Javascript: Part I - Web platform over frameworks](https://alistapart.com/article/responsible-javascript-part-1/)
|
||||
* [JavaScript Loading Priorities in Chrome](https://addyosmani.com/blog/script-priorities/)
|
||||
* [Idle Until Urgent](https://philipwalton.com/articles/idle-until-urgent/)
|
||||
* [Browser painting and considerations for web performance](https://css-tricks.com/browser-painting-and-considerations-for-web-performance/)
|
||||
* [The Cost Of JavaScript In 2018](https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4) ([Video](https://www.youtube.com/watch?v=i5R7giitymk))
|
||||
* [Examining Web Worker Performance](https://www.loxodrome.io/post/web-worker-performance/)
|
||||
* [Front-End Performance Checklist](https://github.com/thedaviddias/Front-End-Performance-Checklist)
|
||||
* [jankfree](http://jankfree.org/)
|
||||
* [What forces layout/reflow?](https://gist.github.com/paulirish/5d52fb081b3570c81e3a)
|
||||
* [Using requestIdleCallback](https://developers.google.com/web/updates/2015/08/using-requestidlecallback)
|
||||
* [Optimize Javascript Execution](https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution)
|
||||
* [Why Web Developers Need to Care about Interactivity](https://philipwalton.com/articles/why-web-developers-need-to-care-about-interactivity/)
|
||||
* [Improving Performance with the Paint Timing API](https://www.sitepen.com/blog/2017/10/06/improving-performance-with-the-paint-timing-api)
|
||||
* [Deploying ES2015+ Code in Production Today](https://philipwalton.com/articles/deploying-es2015-code-in-production-today/)
|
||||
* [Performant Web Animations and Interactions: Achieving 60 FPS](https://blog.algolia.com/performant-web-animations/)
|
||||
* [JavaScript Start-up Performance](https://medium.com/reloading/javascript-start-up-performance-69200f43b201)
|
||||
* [Performant Parallaxing](https://developers.google.com/web/updates/2016/12/performant-parallaxing)
|
||||
* [The Anatomy of a Frame](https://aerotwist.com/blog/the-anatomy-of-a-frame/)
|
||||
* [The future of loading CSS](https://jakearchibald.com/2016/link-in-body/)
|
||||
* [4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them](https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/)
|
||||
* [The cost of frameworks](https://aerotwist.com/blog/the-cost-of-frameworks/)
|
||||
* [FLIP Your Animations](https://aerotwist.com/blog/flip-your-animations/)
|
||||
@@ -7,4 +7,4 @@ Always check if the framework CSS you want to use don't already has a reset / no
|
||||
- [UnCSS Online](https://uncss-online.com/)
|
||||
- [PurifyCSS](https://github.com/purifycss/purifycss)
|
||||
- [PurgeCSS](https://github.com/FullHuman/purgecss)
|
||||
- [Chrome DevTools Coverage](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage)
|
||||
- [Chrome DevTools Coverage](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage)
|
||||
@@ -7,4 +7,4 @@ Small devices don't need images bigger than their viewport. It's recommended to
|
||||
- Create different image sizes for the devices you want to target
|
||||
- Use `srcset` and `picture` to deliver multiple variants of each image.
|
||||
|
||||
- [Responsive images - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)
|
||||
- [Responsive images - Learn web development | MDN](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
> Set width and height attributes on `<img>` if the final rendered image size is known.
|
||||
|
||||
If height and width are set, the space required for the image is reserved when the page is loaded. However, without these attributes, the browser does not know the size of the image, and cannot reserve the appropriate space to it. The effect will be that the page layout will change during loading (while the images load).
|
||||
If height and width are set, the space required for the image is reserved when the page is loaded. However, without these attributes, the browser does not know the size of the image, and cannot reserve the appropriate space to it. The effect will be that the page layout will change during loading (while the images load).
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Squoosh.app is a web app that allows you to compress images using a variety of codecs. It is built by Google Chrome team and is open source.
|
||||
|
||||
- [Squoosh.app](https://squoosh.app/)
|
||||
- [Squoosh.app](https://squoosh.app/)
|
||||
@@ -0,0 +1,6 @@
|
||||
# Use CDN
|
||||
|
||||
Use a CDN to serve your static assets. This will reduce the load on your server and improve the performance of your site.
|
||||
|
||||
- [10 Tips to Optimize CDN Performance - CDN Planet](https://www.cdnplanet.com/blog/10-tips-optimize-cdn-performance/)
|
||||
- [HTTP Caching | Web Fundamentals | Google Developers](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching)
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Set HTTP headers to avoid expensive number of roundtrips between your browser and the server.
|
||||
|
||||
- [Using cache-control for browser caching](https://varvy.com/pagespeed/cache-control.html)
|
||||
- [Using cache-control for browser caching](https://varvy.com/pagespeed/cache-control.html)
|
||||
@@ -6,4 +6,4 @@ HTTPS is not only for ecommerce websites, but for all websites that are exchangi
|
||||
- [Enabling HTTPS Without Sacrificing Your Web Performance - Moz](https://moz.com/blog/enabling-https-without-sacrificing-web-performance)
|
||||
- [How HTTPS Affects Website Performance](https://wp-rocket.me/blog/https-affects-website-performance/)
|
||||
- [HTTP versus HTTPS versus HTTP2 - The real story | Tune The Web](https://www.tunetheweb.com/blog/http-versus-https-versus-http2/)
|
||||
- [HTTP vs HTTPS — Test them both yourself](https://www.httpvshttps.com/)
|
||||
- [HTTP vs HTTPS — Test them both yourself](https://www.httpvshttps.com/)
|
||||
@@ -15,5 +15,6 @@ JavaScript blocks the normal parsing of the HTML document, so when the parser re
|
||||
- Add `async` (if the script don't rely on other scripts) or `defer` (if the script relies upon or relied upon by an async script) as an attribute to your script tag.
|
||||
- If you have small scripts, maybe use inline script place above async scripts.
|
||||
|
||||
|
||||
- [Remove Render-Blocking JavaScript](https://developers.google.com/speed/docs/insights/BlockingJS)
|
||||
- [Defer loading JavaScript](https://varvy.com/pagespeed/defer-loading-javascript.html)
|
||||
- [Defer loading JavaScript](https://varvy.com/pagespeed/defer-loading-javascript.html)
|
||||
@@ -1,7 +1,7 @@
|
||||
# Preconnect on Fonts
|
||||
|
||||
```html
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
```
|
||||
|
||||
When you arrived on a website, your device needs to find out where your site lives and which server it needs to connect with. Your browser had to contact a DNS server and wait for the lookup complete before fetching the resource (fonts, CSS files...). Prefetches and preconnects allow the browser to lookup the DNS information and start establishing a TCP connection to the server hosting the font file. This provides a performance boost because by the time the browser gets around to parsing the css file with the font information and discovering it needs to request a font file from the server, it will already have pre-resolved the DNS information and have an open connection to the server ready in its connection pool.
|
||||
@@ -14,4 +14,4 @@ When you arrived on a website, your device needs to find out where your site liv
|
||||
- [Make Your Site Faster with Preconnect Hints | Viget](https://www.viget.com/articles/make-your-site-faster-with-preconnect-hints/)
|
||||
- [Ultimate Guide to Browser Hints: Preload, Prefetch, and Preconnect - MachMetrics Speed Blog](https://www.machmetrics.com/speed-blog/guide-to-browser-hints-preload-preconnect-prefetch/)
|
||||
- [A Comprehensive Guide to Font Loading Strategies—zachleat.com](https://www.zachleat.com/web/comprehensive-webfonts/#font-face)
|
||||
- [typekit/webfontloader: Web Font Loader gives you added control when using linked fonts via @font-face.](https://github.com/typekit/webfontloader)
|
||||
- [typekit/webfontloader: Web Font Loader gives you added control when using linked fonts via @font-face.](https://github.com/typekit/webfontloader)
|
||||
@@ -1,3 +1,3 @@
|
||||
# Use same Protocol
|
||||
|
||||
Avoid having your website serving files coming from source using HTTP on your website which is using HTTPS for example. If your website is using HTTPS, external files should come from the same protocol.
|
||||
Avoid having your website serving files coming from source using HTTP on your website which is using HTTPS for example. If your website is using HTTPS, external files should come from the same protocol.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
You are using Service Workers in your PWA to cache data or execute possible heavy tasks without impacting the user experience of your application.
|
||||
|
||||
- [Service Workers: an Introduction | Web Fundamentals | Google Developers](https://developers.google.com/web/fundamentals/primers/service-workers/)
|
||||
- [Measuring the Real-world Performance Impact of Service Workers | Web | Google Developers](https://developers.google.com/web/showcase/2016/service-worker-perf)
|
||||
- [Service Workers: an Introduction | Web Fundamentals | Google Developers](https://developers.google.com/web/fundamentals/primers/service-workers/)
|
||||
- [Measuring the Real-world Performance Impact of Service Workers | Web | Google Developers](https://developers.google.com/web/showcase/2016/service-worker-perf)
|
||||
- [What Are Service Workers and How They Help Improve Performance](https://www.keycdn.com/blog/service-workers/)
|
||||
- [How does a service worker work? - YouTube](https://www.youtube.com/watch?v=__xAtWgfzvc)
|
||||
- [How does a service worker work? - YouTube](https://www.youtube.com/watch?v=__xAtWgfzvc)
|
||||
@@ -8,4 +8,4 @@ Check before buying your new font that the provider gives you the WOFF2 format.
|
||||
- [Create Your Own @font-face Kits » Font Squirrel](https://www.fontsquirrel.com/tools/webfont-generator)
|
||||
- [IcoMoon App - Icon Font, SVG, PDF & PNG Generator](https://icomoon.io/app/)
|
||||
- [Using @font-face | CSS-Tricks](https://css-tricks.com/snippets/css/using-font-face/?ref=frontendchecklist)
|
||||
- [Can I use... WOFF2](https://caniuse.com/#feat=woff2)
|
||||
- [Can I use... WOFF2](https://caniuse.com/#feat=woff2)
|
||||
@@ -1,3 +1,3 @@
|
||||
# webpagetest.org
|
||||
|
||||
Webpagetest.org is a website performance testing tool that allows users to test the load time and performance of web pages. It provides a wealth of information about the page load, including load time, Speed Index, and filmstrip view of the page load, as well as a breakdown of the page components and their load times. It also allows for testing from multiple locations and browsers. The test results can be shared and compared with others. It is widely used by developers, marketers, and website owners to improve the performance and user experience of their websites.
|
||||
Webpagetest.org is a website performance testing tool that allows users to test the load time and performance of web pages. It provides a wealth of information about the page load, including load time, Speed Index, and filmstrip view of the page load, as well as a breakdown of the page components and their load times. It also allows for testing from multiple locations and browsers. The test results can be shared and compared with others. It is widely used by developers, marketers, and website owners to improve the performance and user experience of their websites.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
jsonUrl: "/jsons/best-practices/frontend-performance.json"
|
||||
pdfUrl: "/pdfs/best-practices/frontend-performance.pdf"
|
||||
order: 1
|
||||
featuredTitle: "Frontend Performance"
|
||||
featuredDescription: "Frontend Performance Best Practices"
|
||||
isNew: true
|
||||
isUpcoming: false
|
||||
title: "Frontend Performance"
|
||||
description: "Detailed list of best practices to improve your frontend performance"
|
||||
dimensions:
|
||||
width: 968
|
||||
height: 1270.89
|
||||
schema:
|
||||
headline: "Frontend Performance Best Practices"
|
||||
description: "Detailed list of best practices to improve the frontend performance of your website. Each best practice carries further details and how to implement that best practice."
|
||||
imageUrl: "https://roadmap.sh/best-practices/frontend-performance.png"
|
||||
datePublished: "2023-01-23"
|
||||
dateModified: "2023-01-23"
|
||||
seo:
|
||||
title: "Frontend Performance Best Practices"
|
||||
description: "Detailed list of best practices to improve the frontend performance of your website. Each best practice carries further details and how to implement that best practice."
|
||||
keywords:
|
||||
- "frontend performance"
|
||||
- "frontend performance best practices"
|
||||
- "frontend performance checklist"
|
||||
- "frontend checklist"
|
||||
- "make performant frontends"
|
||||
---
|
||||
@@ -2,8 +2,6 @@ export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
// To selectively enable/disable debug logs
|
||||
__DEBUG__: boolean;
|
||||
gtag: any;
|
||||
fireEvent: (props: GAEventType) => void;
|
||||
}
|
||||
@@ -29,10 +27,6 @@ window.fireEvent = (props: GAEventType) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.__DEBUG__) {
|
||||
console.log('Analytics event fired', props);
|
||||
}
|
||||
|
||||
window.gtag('event', action, {
|
||||
event_category: category,
|
||||
event_label: label,
|
||||
|
||||
@@ -3,16 +3,18 @@ import Popup from './Popup/Popup.astro';
|
||||
import CaptchaFields from './Captcha/CaptchaFields.astro';
|
||||
---
|
||||
|
||||
<Popup id='download-popup' title='Download' subtitle='Enter your email below to receive the download link.'>
|
||||
<Popup
|
||||
id='download-popup'
|
||||
title='Download'
|
||||
subtitle='Enter your email below to receive the download link.'
|
||||
>
|
||||
<form
|
||||
action='https://news.roadmap.sh/subscribe'
|
||||
action='https://newsletter.roadmap.sh/subscribe'
|
||||
method='POST'
|
||||
accept-charset='utf-8'
|
||||
target='_blank'
|
||||
captcha-form
|
||||
>
|
||||
<input type='hidden' name='gdpr' value='true' />
|
||||
|
||||
<input
|
||||
type='email'
|
||||
name='email'
|
||||
@@ -40,11 +42,13 @@ import CaptchaFields from './Captcha/CaptchaFields.astro';
|
||||
</Popup>
|
||||
|
||||
<script>
|
||||
document.querySelector('[submit-download-form]')?.addEventListener('click', () => {
|
||||
window.fireEvent({
|
||||
category: 'Subscription',
|
||||
action: 'Submitted Popup Form',
|
||||
label: 'Download Roadmap Popup',
|
||||
document
|
||||
.querySelector('[submit-download-form]')
|
||||
?.addEventListener('click', () => {
|
||||
window.fireEvent({
|
||||
category: 'Subscription',
|
||||
action: 'Submitted Popup Form',
|
||||
label: 'Download Roadmap Popup',
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||