Skip to content
This repository has been archived by the owner on Feb 3, 2025. It is now read-only.

Commit

Permalink
feat: migrate to Next.js
Browse files Browse the repository at this point in the history
  • Loading branch information
irfan-maulana-tkp committed Sep 30, 2020
1 parent 5d265e8 commit 0995c42
Show file tree
Hide file tree
Showing 46 changed files with 3,693 additions and 7,996 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_BASE_PATH="http://localhost:3000"
Empty file added .env.local
Empty file.
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_BASE_PATH="https://webperf-ecommerce-id.surge.sh"
33 changes: 18 additions & 15 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
module.exports = {
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
'plugin:prettier/recommended',
],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
rules: {
'@typescript-eslint/ban-ts-ignore': 'warn',
'@typescript-eslint/camelcase': 'warn',
'@typescript-eslint/no-var-requires': 'warn'
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
},
parserOptions: {
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
ecmaFeatures: {
jsx: true, // Allows for the parsing of JSX
},
},
rules: {
'react/prop-types': 'warn',
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
},
};
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Audit web perf

on:
schedule:
- cron: '0 12 * * 3'
- cron: '0 3 * * 0'

jobs:
run_audit:
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:
git config --local user.email "[email protected]"
git config --local user.name "mazipan"
git add -A
git commit -m "👋 Generated report 🚀"
git commit -m "👋 Generated report 🚀" --no-verify
- name: GitHub Push
uses: ad-m/[email protected]
Expand All @@ -71,8 +71,8 @@ jobs:
directory: ./reports
force: true

- name: Build UI
run: yarn build
- name: Build Web UI
run: yarn export

- name: Deploy UI to surge.sh
uses: dswistowski/surge-sh-action@v1
Expand Down
24 changes: 24 additions & 0 deletions components/FooterSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

const Footer = (): React.ReactElement => {
return (
<footer className="bg-gray-900">
<ul className="flex items-center justify-between max-w-4xl p-4 mx-auto text-sm text-white md:p-8">
<li>
Code by{' '}
<a href="https://mazipan.space/" target="_blank" rel="noopenner noreferrer" className="font-bold">
Irfan Maulana
</a>
</li>

<li>
<a href="https://github.com/mazipan" target="_blank" rel="noopenner noreferrer" className="font-bold">
GitHub
</a>
</li>
</ul>
</footer>
);
};

export default Footer;
18 changes: 18 additions & 0 deletions components/HeaderSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import Link from 'next/link';

const Header = (): React.ReactElement => {
return (
<header className="bg-gray-900">
<div className="flex flex-wrap items-center justify-between max-w-4xl p-4 mx-auto md:flex-no-wrap">
<div className="flex items-center">
<Link href="/">
<a className="text-xl font-bold text-white">⚡ E-Commerce Web Perf</a>
</Link>
</div>
</div>
</header>
);
};

export default Header;
48 changes: 48 additions & 0 deletions components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import Head from 'next/head';

import Header from './HeaderSection';
import Footer from './FooterSection';
import { BASE_PATH } from '../constants';

const title = 'E-Commerce Web Perf';
const titleWithIcon = `⚡️ ${title}`;
const desc = 'Web Perf Comparison for E-Commerce in Indonesia';
const url = BASE_PATH;

const Layout = ({ children }): React.ReactElement => {
return (
<>
<Head>
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="theme-color" content="#7B341E" />
<title key="title">{title}</title>
<meta key="description" name="description" content={desc} />

<meta name="twitter:card" content="summary_large_image" />
{/* <meta name="twitter:image" content={metaImg} /> */}
<meta name="twitter:site" content="@Maz_Ipan" />
<meta key="twitter:title" name="twitter:title" content={titleWithIcon} />
<meta key="twitter:description" name="twitter:description" content={desc} />

<meta key="og:title" property="og:title" content={titleWithIcon} />
<meta key="og:description" property="og:description" content={desc} />
<meta key="og:url" property="og:url" content={url} />
<meta property="og:type" content="website" />
{/* <meta property="og:image" content={metaImg} /> */}

<link rel="apple-touch-icon" sizes="180x180" href={`${url}/apple-touch-icon.png`} />
<link rel="icon" type="image/png" sizes="32x32" href={`${url}/favicon-32x32.png`} />
<link rel="icon" type="image/png" sizes="16x16" href={`${url}/favicon-16x16.png`} />
<link rel="manifest" href={`${url}/site.webmanifest`} />
</Head>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-1 w-full max-w-4xl p-4 mx-auto">{children}</main>
<Footer />
</div>
</>
);
};

export default Layout;
3 changes: 3 additions & 0 deletions constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const basePath = process.env.NEXT_PUBLIC_BASE_PATH;

export const BASE_PATH = basePath;
2 changes: 1 addition & 1 deletion src/cronjob/ecommerce.ts → cronjob/ecommerce.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EcommerceItem } from './types';
import { EcommerceItem } from '../types';

const data: EcommerceItem[] = [
{
Expand Down
28 changes: 28 additions & 0 deletions cronjob/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import fs from 'fs';
import path from 'path';
import appRootDir from 'app-root-dir';

export const REPORT_DIR = path.join(path.resolve(appRootDir.get()), `/reports`);
export const REPORT_JSON_FILE = path.join(REPORT_DIR, `/output.json`);
export const REPORT_TS_FILE = path.join(REPORT_DIR, `/output.ts`);

export const writeFile = (filename: string, content: string): Promise<any> => {
return new Promise(function (resolve, reject) {
fs.writeFile(filename, content, 'utf-8', function (err) {
if (err) reject(err);
else resolve(content);
});
});
};

export const writeNewReport = (content: any): void => {
writeFile(REPORT_JSON_FILE, JSON.stringify(content, null, 2));
writeFile(REPORT_TS_FILE, `export default ${JSON.stringify(content, null, 2)}`);
};

export const readFileReport = (onSuccess): void => {
fs.readFile(REPORT_JSON_FILE, (err, data) => {
if (err) throw err;
onSuccess(data);
});
};
68 changes: 68 additions & 0 deletions cronjob/lh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import fetch from 'node-fetch';
import { LHResponse } from '../types';

export default async (name: string, url: string, device: string): Promise<LHResponse | null> => {
const URL = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&key=${
process.env.PSI_API_KEY
}&strategy=${device}`;

try {
const resp = await fetch(URL, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});

const result = await resp.json();

if (result) {
const loadingExperience = result?.loadingExperience || {};
const lighthouseResult = result?.lighthouseResult || {};
const categories = lighthouseResult?.categories || {};
const audits = lighthouseResult?.audits || {};

const totalResources = audits['resource-summary']?.details?.items?.[0] || {};

const fieldData = loadingExperience.metrics || {};

const fid = fieldData['FIRST_INPUT_DELAY_MS']?.percentile || 0;
const fmp = audits['first-meaningful-paint']?.numericValue || 0;
const fcp = audits['first-contentful-paint']?.numericValue || 0;
const lcp = audits['largest-contentful-paint']?.numericValue || 0;
const cls = audits['cumulative-layout-shift']?.numericValue || 0;
const fci = audits['first-cpu-idle']?.numericValue || 0;
const tbt = audits['total-blocking-time']?.numericValue || 0;
const tti = audits['interactive']?.numericValue || 0;
const si = audits['speed-index']?.numericValue || 0;

const perf = categories?.performance?.score || 0;
const req = totalResources.requestCount || 0;
const size = totalResources.size || 0;

const response: LHResponse = {
perf,
fid,
lcp,
cls,
fmp,
fcp,
fci,
tbt,
tti,
si,
size,
req,
name,
device,
};

return response;
}
} catch (e) {
console.error('> Error job', e);
}

return null;
};
21 changes: 21 additions & 0 deletions cronjob/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { modifyLatestData } from './utils';
import { writeNewReport, readFileReport } from './file';

export const updateReport = (name: string, device: string, response: any): void => {
const onGetLastData = (data) => {
if (!data) {
console.warn(`> [REPORT] - empty last report\n`);
}

try {
const objectData = JSON.parse(data);
const newValue = modifyLatestData(objectData, response, name, device);

writeNewReport(newValue);
} catch (e) {
console.error(`> [REPORT] - failed write report`, e);
}
};

readFileReport(onGetLastData);
};
36 changes: 10 additions & 26 deletions src/cronjob/job.ts → cronjob/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@ import Table from 'cli-table3';
import data from './ecommerce';
import runLH from './lh';
import { updateReport } from './report';
import { EcommerceItem } from './types';
import { EcommerceItem } from '../types';
import { quantile } from './utils';

const NUMBER_OF_RUN = 5;
const PERCENTILE_NUM = 0.75;

const run = async (name: string, url: string, device: string): Promise<any | null> => {
const runJob = async (name: string, url: string, device: string): Promise<any | null> => {
const results: any[] = [];
const tableLog = new Table({
head: ['Perf', 'TTFB', 'FCP', 'TTI'],
head: ['Perf', 'FID', 'CLS', 'LCP', 'TTI'],
});

for (let i = 0; i < NUMBER_OF_RUN; i++) {
const response = await runLH(name, url, device, i);
const response = await runLH(name, url, device);
if (response) {
results.push(response);
// @ts-ignore
tableLog.push([response.perf, response.ttfb, response.fcp, response.tti]);
tableLog.push([response.perf, response.fid, response.cls, response.lcp, response.tti]);
}
}

Expand All @@ -32,7 +31,8 @@ const run = async (name: string, url: string, device: string): Promise<any | nul
updateReport(name, device, report);
};

const readData = () => {
// Main function, will invoked immediatelly
(() => {
let isSecretNotFound = false;
if (process.env.PSI_API_KEY) {
console.log(`> Found env PSI_API_KEY`);
Expand All @@ -41,26 +41,10 @@ const readData = () => {
console.error(`> env PSI_API_KEY not found`);
}

if (process.env.GIST_TOKEN) {
console.log(`> Found env GIST_TOKEN`);
} else {
isSecretNotFound = true;
console.error(`> env GIST_TOKEN not found`);
}

if (process.env.GIST_ID) {
console.log(`> Found env GIST_ID`);
} else {
isSecretNotFound = true;
console.error(`> env GIST_ID not found`);
}

if (!isSecretNotFound) {
data.map(async (item: EcommerceItem) => {
await run(item.name, item.urlMobile, 'mobile');
await run(item.name, item.urlDesktop, 'desktop');
await runJob(item.name, item.urlMobile, 'mobile');
await runJob(item.name, item.urlDesktop, 'desktop');
});
}
};

readData();
})();
Loading

0 comments on commit 0995c42

Please sign in to comment.