├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── astrospeed
├── bundle.js
├── index.html
└── results.js
├── gitHookInstall.js
├── gitHookUninstall.js
├── package.json
├── public
└── results.js
├── server.js
├── src
├── App.tsx
├── assets
│ └── moreArrow.svg
├── components
│ ├── Card.jsx
│ ├── DialChart.jsx
│ ├── LineChart.jsx
│ ├── ListContainer
│ │ ├── DiagnosticsItem.jsx
│ │ ├── ListContainer.jsx
│ │ └── styles.module.css
│ └── chartdata.js
├── index.tsx
└── styles.css
├── tsconfig.json
├── webpack.config.js
└── website
├── .npmrc
├── astro.config.mjs
├── demo
├── bundle.js
├── index.html
└── results.js
├── package.json
├── public
├── Audrey-Park.webp
├── How-it-works-chart.webp
├── Luke-McInerney.webp
├── Marco-Gonzalez.webp
├── Rami-Abdelghafar.webp
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── astronaut.png
├── colored-logo.png
├── crater-footer.webp
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── favicon.svg
├── moonTelescope.png
├── site.webmanifest
├── space-bg.png
├── tether.png
└── white-logo.png
├── server.js
├── src
├── components
│ ├── Astronaut.astro
│ ├── Button.astro
│ ├── Comparisons.astro
│ ├── ContactCard.astro
│ ├── ContactUs.astro
│ ├── NavBar.astro
│ ├── RocketChart.astro
│ └── SplashPoster.astro
├── env.d.ts
├── layouts
│ └── main.astro
└── pages
│ ├── getstarted.astro
│ ├── index.astro
│ ├── login.astro
│ └── watchdemo.astro
├── tailwind.config.cjs
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | modules: false
7 | }
8 | ],
9 | '@babel/preset-react'
10 | ]
11 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | package-lock.json
5 | .vscode
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | .vscode
5 | package-lock.json
6 | src
7 | .babelrc
8 | tsconfig.json
9 | webpack.config.js
10 | astrospeed/results.js
11 | public/results.js
12 | website
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # astroSpeed
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 | [![Contributors][contributors-shield]][contributors-url]
21 | [![Stargazers][stars-shield]][stars-url]
22 | [![Issues][issues-shield]][issues-url]
23 | [![MIT License][license-shield]][license-url]
24 | [![LinkedIn][linkedin-shield]][linkedin-url]
25 |
26 |
27 |
28 |
29 |
30 |
50 |
51 |
52 |
53 |
54 |
55 | Table of Contents
56 |
57 |
58 | About The Project
59 |
62 |
63 |
64 | Getting Started
65 |
69 |
70 | About
71 |
75 | Roadmap
76 | Contributing & Contacts
77 | Acknowledgments & License
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | ## Summary
87 |
88 | astroSpeed is a data collection and reporting suite that uses the Google Lighthouse SDK to automatically collect application metrics such as Performance and SEO in the background during the development process, after each commit.
89 |
90 |
91 |
92 |
93 |
94 | ### Built With
95 |
96 | * [![Astro][Astro-shield]][Astro-url]
97 | * [![React][React.js]][React-url]
98 | * [![Google LightHouse][GLH-shield]][GLH-url]
99 | * [![TypeScript][Typescript-shield]][Typescript-url]
100 | * [![Tailwind][Tailwind-shield]][Tailwind-url]
101 |
102 |
103 | ## Getting Started
104 |
105 | astroSpeed is available in Node Package Manager and Github. The following steps detail installation from NPM.
106 |
107 | ### Prerequisites
108 |
109 | * Node.js - 14.18.0 or higher.
110 | * Astro - 1.2.0 or higher.
111 | * Git
112 | ### Installation & Usage
113 |
114 | ·
115 | watch the install demo here -or- follow instructions below...
116 |
117 | 1. Start or open an Astro project.
118 | 2. Install astroSpeed into the project as a dev dependency.
119 | ```sh
120 | npm install astrospeed --save-dev
121 | ```
122 | 3. Optional - create astrospeed.config.json in your project's root directory if you want to manually configure astroSpeed. Add 1 or more of the below configurables to the config file, below values indicate the default.
123 | ```json
124 | {
125 | "endpoints": ["/"],
126 | "port": 3000,
127 | "buildCommand": "npm run build",
128 | "outputDir": "dist",
129 | "useVite": 1
130 | }
131 | ```
132 | 4. Commit new changes to your project's git repository. The post-commit Git hook will automatically start the astroSpeed report generation.
133 | ```sh
134 | git commit -a -m "your commit message here"
135 | ```
136 | 5. The path to the newly generated astroSpeed report is written to stdout in your terminal (by default under the "astrospeed" folder in your project's root directory).
137 | ```sh
138 | git commit -a -m "your commit message here"
139 | [main 5958617] your commit message here
140 | 1 files changed, 3 insertions(+), 2 deletions(-)
141 | Astrospeed report in progress...
142 | Astrospeed report available at /astrospeed/index.html
143 | ```
144 | 6. You've launched! Open the report in a web browser to review the latest Google Lighthouse findings and compare them to previous commits.
145 |
146 | 7. Each commit will regenerate the astroSpeed report, appending the latest Google Lighthouse scores to the report.
147 |
148 | ## About
149 | ### Google Lighthouse
150 | Google Lighthouse is an open-source tool developed by Google for understanding the performance, quality, and correctness of your web apps ([see Google Lighthouse on Github](https://github.com/GoogleChrome/lighthouse)). astroSpeed uses Google Lighthouse metrics for Performance, Search Engine Optimization, Best Practices, and Accessibility diagnostics.
151 | ### Git post-commit hook details
152 |
153 | astroSpeed is configured to create a report after every commit using a git hook. astroSpeed's installation script attempts to add a post-commit hook. The installation script will notify you via the terminal if the attempt was successful or not. If successful, the the following line will be added to the file ./.git/hooks/post-commit.
154 | ```sh
155 | npx astrospeed-snap
156 | ```
157 | npx astrospeed-snap can also be used to generate a new report manually whenever you want. If you would would like to remove the post-commit hook, please don't uninstall astroSpeed. Simply remove the hook by using
158 | ```sh
159 | npx astrospeed-hookuninstall
160 | ```
161 | If you change your mind later and wish to reinstate the post-commit hook, you can add it back using
162 | ```sh
163 | npx astrospeed-hookinstall
164 | ```
165 | If you unfortunately decided to stop using astroSpeed, please uninstall the hook BEFORE uninstalling astroSpeed. Failure to do so will result in error messages after every commit about not being able to find astroSpeed. Your commits should still be successful, however. This unfortunate behavior is due to Node Package Manager removing the ability to create post uninstall scripts.
166 |
167 | ## Roadmap
168 |
169 | - [ ] Upload report to astroSpeed.io and receive a shareable link.
170 | - [ ] Add Deep-dive performance metrics.
171 | - [ ] Create user authentication on astroSpeed.io.
172 |
173 | See the [open issues](https://github.com/oslabs-beta/astrospeed/issues) for a full list of proposed features (and known issues).
174 |
175 | ## Contributing
176 |
177 | Contributions are welcome! For feature requests, please create a new issue with the "enhancement" tag.
178 |
179 | To contribute:
180 | 1. Fork the Project.
181 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`).
182 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`). Be sure to Lint your code!
183 | 4. Push to the Branch (`git push origin feature/AmazingFeature`).
184 | 5. Open a Pull Request to [https://github.com/oslabs-beta/astrospeed/](https://github.com/oslabs-beta/astrospeed/).
185 |
186 | Join astroSpeed contributors on [LinkedIn](https://linkedin.com/in/astrospeed).
187 |
188 | ## Acknowledgments & License
189 |
190 | * [OpenSource Labs](https://opensourcelabs.io/)
191 | * [Astro Community](https://discord.com/invite/grF4GTXXYm)
192 | * [You, our users <3](http://www.astrospeed.io)
193 |
194 | Distributed under the MIT License. See `LICENSE` for more information.
195 |
196 | (back to top )
197 |
198 |
199 |
200 |
201 |
202 | [contributors-shield]: https://img.shields.io/github/contributors/oslabs-beta/astrospeed.svg?style=for-the-badge
203 | [contributors-url]: https://github.com/oslabs-beta/astrospeed/graphs/contributors
204 | [forks-shield]: https://img.shields.io/github/forks/oslabs-beta/astrospeed.svg?style=for-the-badge
205 | [forks-url]: https://github.com/oslabs-beta/astrospeed/network/members
206 | [stars-shield]: https://img.shields.io/github/stars/oslabs-beta/astrospeed.svg?style=for-the-badge
207 | [stars-url]: https://github.com/oslabs-beta/astrospeed/stargazers
208 | [issues-shield]: https://img.shields.io/github/issues/oslabs-beta/astrospeed.svg?style=for-the-badge
209 | [issues-url]: https://github.com/oslabs-beta/astrospeed/issues
210 | [license-shield]: https://img.shields.io/github/license/oslabs-beta/astrospeed.svg?style=for-the-badge
211 | [license-url]: https://github.com/oslabs-beta/astrospeed/blob/master/LICENSE.txt
212 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
213 | [linkedin-url]: https://linkedin.com/company/astrospeed
214 | [product-screenshot]: https://i.ibb.co/pz6nzrz/Screen-Shot-2022-10-12-at-3-20-46-PM.png
215 |
216 | [Astro-url]: https://astro.build/
217 | [Astro-shield]: https://i.ibb.co/F8HcbtD/Screen-Shot-2022-10-12-at-12-34-42-PM.png
218 | [React.js]: https://i.ibb.co/MBLkXB6/Screen-Shot-2022-10-12-at-12-21-22-PM.png
219 | [React-url]: https://reactjs.org/
220 | [GLH-shield]: https://i.ibb.co/LRZ9mgh/Screen-Shot-2022-10-12-at-12-23-35-PM.png
221 | [GLH-url]: https://developers.google.com/web
222 | [Typescript-shield]: https://i.ibb.co/7tT9vy1/Screen-Shot-2022-10-12-at-12-25-59-PM.png
223 | [Typescript-url]: https://www.typescriptlang.org
224 | [Tailwind-url]: https://tailwindcss.com
225 | [Tailwind-shield]: https://i.ibb.co/H7M7D3b/Screen-Shot-2022-10-12-at-12-32-22-PM.png
226 |
227 |
--------------------------------------------------------------------------------
/astrospeed/index.html:
--------------------------------------------------------------------------------
1 | astroSpeed Report
--------------------------------------------------------------------------------
/gitHookInstall.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require("fs");
4 | const { resolve } = require("path");
5 |
6 | const hook = "post-commit";
7 |
8 | function installHooks() {
9 | //Make sure there is a .git folder
10 | const gitRoot = resolve(process.env.INIT_CWD + "/.git");
11 | if (fs.existsSync(gitRoot)) {
12 | const hooksDir = resolve(gitRoot, "hooks");
13 | ensureDirExists(hooksDir); //Add hooks folder if it doesn't exist
14 | const hookFile = resolve(hooksDir, hook);
15 | if (fs.existsSync(hookFile)) {
16 | let fileContents = fs.readFileSync(hookFile, 'utf-8');
17 | if (fileContents.includes('npx astrospeed-snap')) {
18 | console.warn("Correct git hook already in place");
19 | } else {
20 | fs.appendFileSync(hookFile, '\nnpx astrospeed-snap\n');
21 | console.log('astroSpeed hook appended to existing post-commit hooks');
22 | }
23 | return;
24 | }
25 | fs.writeFileSync(
26 | hookFile,
27 | `#!/bin/sh\nnpx astrospeed-snap\n`
28 | ); //create hook file
29 | fs.chmodSync(hookFile, "755"); //make hook file executable
30 | console.log("astroSpeed git integration was successful!");
31 | } else {
32 | console.warn("This does not seem to be a git project.");
33 | }
34 | }
35 |
36 | function ensureDirExists(dir) {
37 | fs.existsSync(dir) || fs.mkdirSync(dir);
38 | }
39 |
40 | installHooks();
41 |
--------------------------------------------------------------------------------
/gitHookUninstall.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require("fs");
4 | const { resolve } = require("path");
5 |
6 | const hookString = "npx astrospeed-snap";
7 | //Make sure there is a .git folder
8 | const gitRoot = resolve(process.env.INIT_CWD + "/.git");
9 |
10 | if (fs.existsSync(gitRoot)) {
11 | const hookFile = resolve(gitRoot, "hooks/post-commit");
12 | //check if hook file exists
13 | if (fs.existsSync(hookFile)) {
14 | let fileContents = fs.readFileSync(hookFile, 'utf-8');
15 | //check if our hook string is present in hook file
16 | if (fileContents.includes(hookString)) {
17 | const newContents = fileContents.replaceAll(hookString,'');
18 | fs.writeFileSync(hookFile, newContents, 'utf-8');
19 | console.log('astroSpeed post-commit hooks removed');
20 | } else {
21 | //else hookstring is not present
22 | console.warn("No astroSpeed hooks found in post-commit hooks");
23 | }
24 | } else {
25 | // no post-commit file found in hooks
26 | console.warn('No post-commit hooks found');
27 | }
28 |
29 | } else {
30 | // else no .git folder
31 | console.warn("This does not seem to be a git project.");
32 | }
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astrospeed",
3 | "version": "1.0.0",
4 | "description": "Astrospeed is a performance monitor for your AstroJS codebase",
5 | "main": "index.js",
6 | "scripts": {
7 | "postinstall": "./gitHookInstall.js",
8 | "clean": "rm dist/bundle.js",
9 | "build-dev": "webpack --mode development",
10 | "build-prod": "webpack --mode production",
11 | "start": "webpack-dev-server --mode development --open --hot"
12 | },
13 | "bin": {
14 | "astrospeed-snap": "./server.js",
15 | "astrospeed-hookinstall": "./gitHookInstall.js",
16 | "astrospeed-hookuninstall": "./githookUninstall.js"
17 | },
18 | "author": "",
19 | "license": "ISC",
20 | "dependencies": {
21 | "express": "^4.18.1",
22 | "lighthouse": "^9.6.7",
23 | "puppeteer": "^18.0.4",
24 | "tree-kill": "^1.2.2"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.19.1",
28 | "@babel/preset-env": "^7.19.1",
29 | "@babel/preset-react": "^7.18.6",
30 | "@svgr/webpack": "^6.3.1",
31 | "@types/react": "^18.0.21",
32 | "@types/react-dom": "^18.0.6",
33 | "babel-loader": "^8.2.5",
34 | "css-loader": "^6.7.1",
35 | "html-webpack-plugin": "^5.5.0",
36 | "react": "^18.2.0",
37 | "react-apexcharts": "^1.4.0",
38 | "react-dom": "^18.2.0",
39 | "style-loader": "^3.3.1",
40 | "ts-loader": "^9.4.1",
41 | "typescript": "^4.8.3",
42 | "url-loader": "^4.1.1",
43 | "webpack": "^5.74.0",
44 | "webpack-cli": "^4.10.0",
45 | "webpack-dev-server": "^4.11.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const lighthouse = require('lighthouse');
4 | const puppeteer = require('puppeteer');
5 | const express = require('express');
6 | const path = require('path');
7 | const fs = require('fs');
8 | const { execSync, exec } = require("child_process");
9 | const kill = require('tree-kill');
10 |
11 | const { endpoints, port, buildCommand, outputDir, localServer } = readConfig();
12 | let server;
13 | let viteProc;
14 |
15 | function readConfig() {
16 | //check if user-defined custom config exists, else use defaults
17 | const defaultCfg = {
18 | endpoints: ['/index.html'],
19 | port: 3000,
20 | buildCommand: 'npm run build',
21 | outputDir: 'dist',
22 | localServer: 'vite'
23 | }
24 | try {
25 | const config = fs.readFileSync(path.resolve(path.join(__dirname, '../../astrospeed.config.json')));
26 | return Object.assign({}, defaultCfg, JSON.parse(config));
27 | } catch (err){
28 | return defaultCfg
29 | }
30 | }
31 |
32 | function readExistingData () {
33 | //check if results.js already exists, if so return the contents, otherwise return empty array
34 | try {
35 | const pathToFile = './astrospeed/results.js'
36 | const oldData = fs.readFileSync(pathToFile);
37 | //remove the window.results assignment to just get the array
38 | const oldDataParsed = oldData.slice(16)
39 | return JSON.parse(oldDataParsed);
40 | } catch (err){
41 | return {};
42 | }
43 | }
44 |
45 | console.log('Astrospeed report in progress...')
46 |
47 | buildApp();
48 | serveAppAndRunLHR();
49 |
50 | //build the user's astro app to the 'dist' folder
51 | function buildApp() {
52 | if (localServer == 'vite'){
53 | execSync('npx astro build')
54 | viteProc = exec('npx astro preview');
55 | } else{
56 | execSync(buildCommand)
57 | }
58 |
59 | }
60 |
61 | async function serveAppAndRunLHR() {
62 | if (localServer == 'vite') {
63 | getReport(endpoints)
64 | } else {
65 | //serve the astro app on port 3500
66 | const app = express();
67 | app.use('*', express.static(outputDir));
68 | server = app.listen(port, () => getReport(endpoints));
69 | }
70 | }
71 |
72 | async function getLighthouseResultsPuppeteer(endpoints) {
73 | const chrome = await puppeteer.launch({args: ['--remote-debugging-port=9224'],});
74 | const options = {
75 | logLevel: 'silent',
76 | output: 'html',
77 | maxWaitForLoad: 10000,
78 | port: 9224
79 | };
80 | const runnerResults = {};
81 |
82 | for (let i = 0; i < endpoints.length; i++) {
83 | const result = await lighthouse(`http://localhost:${port}` + endpoints[i], options)
84 | runnerResults[endpoints[i]] = result.lhr;
85 | }
86 | // const runnerResult = await lighthouse(url, options);
87 | await chrome.close();
88 | return runnerResults;
89 | }
90 |
91 | function getCommitDetails() {
92 | //get latest commit from git log, format it to include commit body (%B), timestamp (%ai), hash (%h)
93 | let commitMsg = execSync('git log -1 --pretty="%B%ai%n%h"').toString().replace(/\n/g,',').split(',').slice(0,3);
94 | let commitProperties = ['msg', 'time', 'hash']
95 | let newCommitData = {};
96 | for (let i = 0; i < 3; i++) {
97 | newCommitData[commitProperties[i]] = commitMsg[i];
98 | }
99 | //return latest object with latest commit details
100 | return newCommitData;
101 | }
102 |
103 | async function getReport(endpoints) {
104 |
105 | //use puppeteer to get lighthouse results object and store it in lhr
106 | const lhr = await getLighthouseResultsPuppeteer(endpoints);
107 | //close express server after lighthouse returns results
108 | if (localServer != 'vite') server.close();
109 | else kill(viteProc.pid)
110 | // read results.js
111 | const data = readExistingData();
112 | //remove unused screenshots from lhr to save space
113 | endpoints.forEach(endpoint => {
114 | lhr[endpoint]['audits']['screenshot-thumbnails']['details'] = null;
115 | lhr[endpoint]['audits']['final-screenshot']['details']['data'] = null;
116 | lhr[endpoint]['audits']['full-page-screenshot']['details'] = null;
117 | //add git details to the lighthouse report under key 'git'
118 | lhr[endpoint]['git'] = getCommitDetails();
119 |
120 | if (!(endpoint in data)) data[endpoint] = [lhr[endpoint]]
121 | else {
122 | data[endpoint].push(lhr[endpoint])
123 | }
124 | })
125 | //resultsOutput is a JS expression that assigns window.results to the data array.
126 | const resultsOutput = 'window.results = ' + JSON.stringify(data);
127 | //outputDir is the astro project two levels up + folder 'astrospeed'. (cwd is inside node_modules/astrospeed)
128 | const outputDir = path.resolve(path.join(__dirname, '../../astrospeed/'));
129 |
130 | if (!fs.existsSync(outputDir)) {
131 | //if the outputDir doesn't exist, create it
132 | fs.mkdirSync(outputDir)
133 | }
134 | //write resultsOutput to results.js
135 | fs.writeFileSync('./astrospeed/results.js', resultsOutput);
136 |
137 | if (!fs.existsSync(path.join(outputDir, 'bundle.js')) || !fs.existsSync(path.join(outputDir, 'index.html'))) {
138 | //if bundle.js or index.html doesn't exist in the astro project folder under 'astrospeed' dir, copy them over from node_modules/astrospeed
139 | fs.copyFileSync(path.resolve(path.join(__dirname, './astrospeed/index.html')), path.join(outputDir, 'index.html'));
140 | fs.copyFileSync(path.resolve(path.join(__dirname, './astrospeed/bundle.js')), path.join(outputDir, 'bundle.js'));
141 | }
142 |
143 | //write to user's terminal the path of the astrospeed report.
144 | console.log('Astrospeed report available at', path.resolve(__dirname, '../../astrospeed/index.html'))
145 | }
146 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { useState } from 'react';
3 | const lhr = (window as any).results;
4 | import ListContainer from "./components/ListContainer/ListContainer.jsx"
5 | import LineChart from "./components/LineChart.jsx";
6 | import DialChart from "./components/DialChart.jsx";
7 | import Card from "./components/Card.jsx"
8 |
9 | // interface Props {
10 | // currentMetric: string
11 | // }
12 |
13 | class App extends React.Component<{}, {currentMetric: string, currentEndpoint: string}> {
14 | constructor(props: any) {
15 | super(props);
16 | this.state = {
17 | currentMetric: 'performance',
18 | currentEndpoint: Object.keys(lhr)[0]
19 | };
20 | }
21 | render() {
22 | // const [currentMetric, setcurrentMetric] = useState('Performance');
23 |
24 | const reportTime = lhr[this.state.currentEndpoint][lhr[this.state.currentEndpoint].length - 1].git.time;
25 | const currPerf = lhr[this.state.currentEndpoint][lhr[this.state.currentEndpoint].length-1].categories.performance.score * 100
26 | const currSeo = lhr[this.state.currentEndpoint][lhr[this.state.currentEndpoint].length-1].categories.seo.score * 100;
27 | const currBP = lhr[this.state.currentEndpoint][lhr[this.state.currentEndpoint].length-1].categories['best-practices'].score * 100;
28 | const currAcc = lhr[this.state.currentEndpoint][lhr[this.state.currentEndpoint].length-1].categories.accessibility.score * 100;
29 |
30 | const divStyle = {
31 | display:'flex'
32 | }
33 |
34 | const availableEndpoints = Object.keys(lhr).map(endpoint => this.setState({currentEndpoint: endpoint})}>{endpoint} )
35 |
36 | return (
37 | <>
38 |
39 |
40 | {/* */}
41 |
63 |
64 | {/* */}
65 |
66 |
67 |
Current Commit Metrics
68 | {/*
{reportTime} */}
69 |
70 |
71 |
72 | this.setState({currentMetric: 'performance'})}
76 | data={currPerf}
77 | />
78 | this.setState({currentMetric: 'seo'})}
82 | // onClick={() => this.setState({currentMetric: 'seo'})}
83 | data={currSeo}
84 | />
85 | this.setState({currentMetric: 'best-practices'})}
89 | data={currBP}
90 | />
91 | this.setState({currentMetric: 'accessibility'})}
95 | data={currAcc}
96 | />
97 |
98 |
99 |
100 |
History & Diagnostics
101 | {/*
*/}
102 |
103 |
104 |
105 |
106 |
Commit History
107 |
108 |
109 |
110 |
111 |
112 |
113 |
Current Commit Details
114 |
115 |
116 |
117 |
118 |
119 |
120 | Metrics via Google Lighthouse. Report generated at: {reportTime}
121 |
122 |
123 |
124 |
125 |
126 | >
127 | )
128 |
129 | // // const {fetchTime} = lhr[lhr.length-1];
130 | // // const reportDate = new Date(fetchTime.slice(0, -1));
131 | // const reportTime = git_commits[git_commits.length - 1][1]
132 | // return (
133 | // <>
134 | // {/*
135 | // Hello {name}
136 | // */}
137 | // Report generated at: {reportTime}
138 | // Performance: {lhr[lhr.length-1].categories.performance.score}
139 | // Search Engine Optimization (SEO): {lhr[lhr.length-1].categories.seo.score}
140 |
141 | // >
142 | // );
143 | }
144 | }
145 |
146 | export default App;
147 |
--------------------------------------------------------------------------------
/src/assets/moreArrow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Card.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import DialChart from "./DialChart";
3 |
4 | export default function Card (props) {
5 | const { name, data, icon, diagnosticsState } = props;
6 | return (
7 |
8 |
9 |
{name}
10 |
{icon}
11 |
12 | {/*
*/}
13 | { data }
14 |
15 | )
16 | }
--------------------------------------------------------------------------------
/src/components/DialChart.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactApexChart from "react-apexcharts";
3 |
4 |
5 | class DialChart extends React.Component {
6 | constructor(props) {
7 | super(props);
8 |
9 | this.state = {
10 | series: [props.data],
11 | options: {
12 | chart: {
13 | height: 350,
14 | type: 'radialBar',
15 | offsetY: -10
16 | },
17 | plotOptions: {
18 | radialBar: {
19 | startAngle: -135,
20 | endAngle: 135,
21 | dataLabels: {
22 | name: {
23 | fontSize: '6px',
24 | color: '#000000',
25 | offsetY: 120
26 | },
27 | value: {
28 | offsetY: -10,
29 | fontSize: '15px',
30 | color: undefined,
31 | formatter: function (val) {
32 | return val + "%";
33 | }
34 | }
35 | }
36 | }
37 | },
38 | fill: {
39 | colors: ['#3700A4', '#000000'],
40 | type: 'gradient',
41 | gradient: {
42 | shade: 'dark',
43 | shadeIntensity: 0.15,
44 | inverseColors: false,
45 | opacityFrom: 1,
46 | opacityTo: 1,
47 | stops: [0, 50, 65, 91]
48 | },
49 | },
50 | stroke: {
51 | dashArray: 4
52 | },
53 | labels: [' '],
54 | // labels: [`${props.name}`],
55 | },
56 |
57 |
58 | };
59 | }
60 |
61 | render() {
62 | const divStyle = {
63 | width: '50%',
64 | }
65 | return (
66 |
67 |
68 |
69 | );
70 | }
71 | }
72 |
73 | export default DialChart;
74 |
75 |
--------------------------------------------------------------------------------
/src/components/LineChart.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactApexChart from "react-apexcharts";
3 | import { lineChartData, lineChartOptions } from "./chartdata";
4 | const lhr = window.results;
5 |
6 |
7 | class LineChart extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | chartData: [],
13 | chartOptions: {},
14 | currentEndpoint: ''
15 | };
16 | }
17 |
18 |
19 | componentDidMount() {
20 | const currentChartData = [];
21 | for (let i = 0; i < 4; i++) {
22 | currentChartData.push(Object.assign({}, lineChartData[i], {data: lineChartData[i]['data'][this.props.currentEndpoint]}))
23 | }
24 | // console.log(lhr[this.props.currentEndpoint][0])
25 | const currentEndpoint = this.props.currentEndpoint;
26 | lineChartOptions.tooltip.x = {
27 | formatter: function(val) {
28 | return `Commit #${val} ${lhr[currentEndpoint][Number(val) - 1].git.time} ${lhr[currentEndpoint][Number(val) - 1].git.msg}`
29 | }
30 | }
31 | this.setState({
32 | chartData: currentChartData,
33 | chartOptions: lineChartOptions,
34 | currentEndpoint: currentEndpoint
35 | });
36 | }
37 |
38 | componentDidUpdate() {
39 | if (this.state.currentEndpoint != this.props.currentEndpoint) {
40 | const currentEndpoint = this.props.currentEndpoint;
41 | const currentChartData = [];
42 | for (let i = 0; i < 4; i++) {
43 | currentChartData.push(Object.assign({}, lineChartData[i], {data: lineChartData[i]['data'][this.props.currentEndpoint]}))
44 | }
45 |
46 | lineChartOptions.tooltip.x = {
47 | formatter: function(val) {
48 | return `Commit #${val} ${lhr[currentEndpoint][Number(val) - 1].git.time} ${lhr[currentEndpoint][Number(val) - 1].git.msg}`
49 | }
50 | }
51 |
52 | this.setState({
53 | chartData: currentChartData,
54 | chartOptions: lineChartOptions,
55 | currentEndpoint: currentEndpoint
56 | });
57 | }
58 | // }
59 |
60 | }
61 |
62 | render() {
63 | const divStyle = {
64 | height: '50vh',
65 | minHeight: '400px',
66 | }
67 | // console.log(this.state.chartData[this.props.currentEndpoint])
68 | return (
69 |
70 |
77 |
78 | );
79 | }
80 | }
81 |
82 | export default LineChart;
83 |
--------------------------------------------------------------------------------
/src/components/ListContainer/DiagnosticsItem.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styles from "./styles.module.css";
3 |
4 | const DiagnosticsItem = ({ data }) => {
5 | const [open, setOpen] = useState(false);
6 |
7 | const handleClick = () => {
8 | setOpen(!open);
9 | };
10 |
11 | // console.log('diag item', data.link)
12 | return (
13 | 0.96 ? styles.green : styles.red
17 | }`}
18 | >
19 |
20 |
21 |
{data.title}
22 |
{data.displayValue || String(data.score * 100) + '%' }
23 |
24 |
25 | {open && (
26 |
29 | )}
30 |
31 | );
32 | };
33 |
34 | export default DiagnosticsItem;
35 |
--------------------------------------------------------------------------------
/src/components/ListContainer/ListContainer.jsx:
--------------------------------------------------------------------------------
1 | //import styles from "./styles.module.css";
2 | import React, { useState, useEffect } from "react";
3 | // import OppurtunityItem from "./OppurtunityItem.jsx";
4 | const lhr = window.results;
5 | import DiagnosticsItem from "./DiagnosticsItem";
6 | import styles from "./styles.module.css";
7 |
8 | const ListContainer = (props) => {
9 | console.log('rendering list container.', props)
10 | /*
11 | Performance breakdown
12 | Audit Weight
13 | First Contentful Paint 10%
14 | Speed Index 10%
15 | Largest Contentful Paint 25%
16 | Time to Interactive 10%
17 | Total Blocking Time 30%
18 | Cumulative Layout Shift 15%
19 | */
20 | const diagnostics = {};
21 | for (const category of Object.keys(lhr[props.currentEndpoint][lhr[props.currentEndpoint].length - 1]['categories'])) {
22 | const refs = lhr[props.currentEndpoint][lhr[props.currentEndpoint].length - 1]['categories'][category]['auditRefs'];
23 | diagnostics[category] = refs.map(ref => {
24 | const fullAudit = Object.assign({}, lhr[props.currentEndpoint][lhr[props.currentEndpoint].length - 1]['audits'][ref.id]);
25 | if (fullAudit.score == null) return null
26 | // console.log(fullAudit.description)
27 | fullAudit.link = fullAudit.description.match(/https:\/\/web.dev.*\//);
28 | fullAudit.description = fullAudit.description.replace(/\[Learn.*/, '')
29 | return fullAudit
30 | }).filter(ref => ref).sort((a, z) => a.score - z.score);
31 | };
32 |
33 | return (
34 |
35 |
36 | {diagnostics[props.currentMetric].map((data) => (
37 |
38 | ))}
39 |
40 |
41 | );
42 | };
43 |
44 | export default ListContainer;
45 |
46 | //line 91
47 | // {opportData.map((data) => (
48 | //
49 | // ))}
50 |
--------------------------------------------------------------------------------
/src/components/ListContainer/styles.module.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .navbar{
4 | width: 100%;
5 | height: 70px;
6 | background-color: rgb(242, 169, 160);
7 | display: flex;
8 | align-items: center;
9 | justify-content: space-between;
10 | }
11 |
12 | .navbar h1{
13 | color: white;
14 | font-size: 25px;
15 | margin-left: 20px;
16 | }
17 |
18 | .form_container{
19 | background-color:rgb(128, 153, 237);
20 | width: 100%;
21 | height: 300px;
22 | border-radius: 5px;
23 | }
24 |
25 | .input{
26 | outline: none;
27 | border: solid;
28 | width: auto;
29 | padding: 15px;
30 | border-radius: 10px;
31 | background-color: rgb(208, 199, 233);
32 | margin: 5px 0;
33 | font-size: 14px;
34 | }
35 |
36 | .inputInsideContainer{
37 | outline: none;
38 | border: solid;
39 | /* width: 92%; */
40 | padding: 15px;
41 | border-radius: 10px;
42 | background-color: rgb(208, 199, 233);
43 | margin: 5px 0;
44 | font-size: 14px;
45 | cursor: pointer;
46 | }
47 |
48 | .inputInsideContainer:hover{
49 | /* background-color: rgb(189, 176, 223); */
50 | opacity: 50%;
51 | /* background-color: #4f35a1; */
52 | }
53 |
54 | .addresses{
55 | background-color: lightgray;
56 | border: 2px double black;
57 | margin-bottom: 20px;
58 | padding: 20px;
59 |
60 | }
61 |
62 |
63 | .white_btn{
64 | border: none;
65 | outline: none;
66 | padding: 12px 0;
67 | background-color: white;
68 | border-radius: 20px;
69 | width: 120px;
70 | font-weight: bold;
71 | font-size: 14px;
72 | cursor: pointer;
73 | margin-right: 20px;
74 | }
75 |
76 | .yours{
77 | margin-left: 30%;
78 | margin-top: 5%;
79 |
80 | }
81 |
82 | .green_btn{
83 | border: none;
84 | outline: none;
85 | padding: 12px 0;
86 | background-color: white;
87 | border-radius: 20px;
88 | width: 180px;
89 | font-weight: bold;
90 | font-size: 14px;
91 | margin: 10px 5px 5px 0px;
92 | cursor: pointer;
93 | }
94 | .span{
95 | font-weight: bold;
96 | }
97 |
98 | .diagnostics {
99 | color: white;
100 | }
101 |
102 | .green {
103 | background-color: #367952;
104 | }
105 |
106 | .red {
107 | background-color: #cc3c43;
108 | }
109 |
110 | .dataTitle {
111 | font-weight: bold;
112 | font-size: 16px;
113 | font-family: system-ui;
114 | color: white;
115 | }
116 |
117 | .description {
118 | font-size: 12px;
119 | font-family: system-ui;
120 | color: white;
121 | }
122 |
123 | .mainItem {
124 | display: flex;
125 | justify-content: space-between;
126 | padding-right: 30px;
127 | color: white;
128 | }
129 |
130 | .bottomData {
131 | margin-top: 10px;
132 | }
133 |
134 | .arrow {
135 | cursor: pointer;
136 | display: flex;
137 | justify-content: end;
138 | color: white;
139 | }
140 |
141 | .arrow::before{
142 | border-style: solid;
143 | border-width: 0.30em 0.30em 0 0;
144 | content: '';
145 | display: inline-block;
146 | height: 0.6em;
147 | left: 0.15em;
148 | top: 0.15em;
149 | vertical-align: top;
150 | width: 0.6em;
151 | transform: rotate(135deg);
152 | }
153 |
154 | .blinking{
155 | animation: blinker 1s infinite;
156 | }
157 | @keyframes blinker {
158 | from { opacity: 1.0; }
159 | 50% { opacity: 0.5; }
160 | to { opacity: 1.0; }
161 | }
--------------------------------------------------------------------------------
/src/components/chartdata.js:
--------------------------------------------------------------------------------
1 | const lhr = window.results;
2 |
3 | const perfScores = {}, seoScores = {}, commitNum = {}, bestPracScores = {}, a11yScores = {};
4 | const endpoints = Object.keys(lhr);
5 |
6 | // {
7 | // 'index.html': [{'performance': 99}]
8 | // }
9 | //iterate through each endpoint
10 | for (let k = 0; k < endpoints.length; k++) {
11 | //iterate through each run in that endpoint
12 | for (let i = 0; i < lhr[endpoints[k]].length; i++) {
13 | if (!(endpoints[k] in commitNum)) commitNum[endpoints[k]] = 0
14 | commitNum[endpoints[k]]++;
15 | if (!(endpoints[k] in perfScores)) perfScores[endpoints[k]] = []
16 | perfScores[endpoints[k]].push(lhr[endpoints[k]][i].categories.performance.score * 100);
17 | if (!(endpoints[k] in seoScores)) seoScores[endpoints[k]] = []
18 | seoScores[endpoints[k]].push(lhr[endpoints[k]][i].categories.seo.score * 100);
19 | if (!(endpoints[k] in bestPracScores)) bestPracScores[endpoints[k]] = []
20 | bestPracScores[endpoints[k]].push(lhr[endpoints[k]][i].categories['best-practices'].score * 100);
21 | if (!(endpoints[k] in a11yScores)) a11yScores[endpoints[k]] = []
22 | a11yScores[endpoints[k]].push(lhr[endpoints[k]][i].categories.accessibility.score * 100);
23 | }
24 | }
25 | // console.log(perfScores)
26 |
27 |
28 | export const lineChartData = [
29 | {
30 | name: "Performance",
31 | data: perfScores,
32 | },
33 | {
34 | name: "Search Engine Optimization",
35 | data: seoScores,
36 | },
37 | {
38 | name: "Best Practices",
39 | data: bestPracScores,
40 | },
41 | {
42 | name: "Accessibility",
43 | data: a11yScores,
44 | },
45 | ];
46 | export const lineChartOptions = {
47 | chart: {
48 | toolbar: {
49 | show: false,
50 | },
51 | },
52 | tooltip: {
53 | // followCursor: false,
54 | theme: "dark",
55 | intersect: false,
56 | x: {
57 | // formatter: function(val) {return `Commit #${val} ${lhr[Number(val) - 1].git.time} ${lhr[Number(val) - 1].git.msg}`}
58 | },
59 | y: {
60 | formatter: function(val) {return `${val}%`}
61 | },
62 |
63 | },
64 | dataLabels: {
65 | enabled: true,
66 | // style: {colors: [ "#E4E684", "#3700A4"]}//['#FFFFFF', '#000000']}
67 | },
68 | stroke: {
69 | curve: "straight",
70 | },
71 | xaxis: {
72 | type: "numeric",
73 | categories: commitNum,
74 | labels: {
75 | style: {
76 | colors: "#c8cfca",
77 | fontSize: "12px",
78 | },
79 | },
80 | title: {text:"Commit #"},
81 | tooltip: {
82 | enabled: false
83 | }
84 | },
85 | yaxis: {
86 | labels: {
87 | style: {
88 | colors: "#c8cfca",
89 | fontSize: "12px",
90 | },
91 | },
92 | max: 100,
93 | min: 0,
94 | tickAmount: 4
95 | },
96 | legend: {
97 | show: true,
98 | },
99 | grid: {
100 | strokeDashArray: 5,
101 | },
102 | fill: {
103 | type: "gradient",
104 | gradient: {
105 | shade: "light",
106 | type: "vertical",
107 | shadeIntensity: 0.5,
108 | gradientToColors: undefined, // optional, if not defined - uses the shades of same color in series
109 | inverseColors: true,
110 | opacityFrom: 0.8,
111 | opacityTo: 0,
112 | stops: [],
113 | },
114 | colors: ["#246dec", "#f5b74f", "#367952", "#cc3c43"],
115 | },
116 | colors: ["#246dec", "#f5b74f", "#367952", "#cc3c43"],
117 | };
118 |
119 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from "react-dom";
3 |
4 | import App from './App';
5 | import "./styles.css";
6 |
7 | var mountNode = document.getElementById("app");
8 | ReactDOM.render( , mountNode);
9 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;300;500&display=swap');
2 |
3 | html {
4 | font-family: 'Montserrat', sans-serif;
5 | }
6 |
7 | h1 {
8 | color: white;
9 | background-color: black;
10 | }
11 |
12 | body {
13 | margin: 0px
14 | }
15 |
16 | .text-primary {
17 | color: #666666;
18 | }
19 |
20 | .text-blue {
21 | color: #246dec;
22 | }
23 |
24 | .text-red {
25 | color: #367952;
26 | }
27 |
28 | .text-green {
29 | color: #367952;
30 | }
31 |
32 | .text-orange {
33 | color: #f5b74f;
34 | }
35 |
36 | .font-weight-bold {
37 | font-weight: 600;
38 | }
39 |
40 | .grid-container {
41 | display: grid;
42 | /* how many columns, width values. nav bar fixed to 260px */
43 | grid-template-columns: 260px 1fr 1fr 1fr;
44 | grid-template-rows: 0fr 3fr;
45 | grid-template-areas:
46 | "sidebar header header header"
47 | "sidebar main main main";
48 | /* 100 of the viewport area */
49 | height: 100vh;
50 | }
51 |
52 | /* -------- HEADER ----------- */
53 |
54 | .header {
55 | grid-area: header;
56 | height:70px;
57 | background-color: #FFFFFF;
58 | display: flex;
59 | align-items: center;
60 | justify-content: space-between;
61 | padding: 0 30px 0 30px;
62 | box-shadow: 0 6px 7px -4px rgba(0, 0, 0, 0.2);
63 | }
64 |
65 | /* responsive view of the menu icon */
66 | .menu-icon {
67 | display: none;
68 | }
69 |
70 | /* -------- SIDE NAV ----------- */
71 |
72 | #sidebar {
73 | grid-area: sidebar;
74 | height:100%;
75 | background-color: #21232d;
76 | color: #9799ab;
77 | overflow-y: auto;
78 | transition: all 0.5s;
79 | -webkit-transition: all 0.5s;
80 | }
81 |
82 | .sidebar-title {
83 | display: flex;
84 | justify-content: space-between;
85 | align-items: center;
86 | padding: 20px 20px 20px 20px;
87 | margin-bottom: 30px;
88 | }
89 |
90 | /* CSS child selector .parent-el > all child spans */
91 | /* want this to be responsive hide*/
92 |
93 | .sidebar-title > span {
94 | display: none;
95 | }
96 |
97 | .sidebar-brand {
98 | margin-top: 15px;
99 | font-size: 20px;
100 | font-weight: 700;
101 | }
102 |
103 | .sidebar-list {
104 | padding: 0px;
105 | margin-top: 15px;
106 | list-style-type: none;
107 | }
108 |
109 | .sidebar-list-item {
110 | padding: 20px 20px 20px 20px;
111 | }
112 | .endpoint {
113 | padding: 10px 10px 10px 50px;
114 | background-color: #333747
115 | }
116 | .endpoint:hover {
117 | background-color: rgba(255, 255, 255, 0.2);
118 | cursor: pointer;
119 | }
120 |
121 | /* use CSS hover selector */
122 | .sidebar-list-item:hover {
123 | background-color: rgba(255, 255, 255, 0.2);
124 | cursor: pointer;
125 | }
126 |
127 | /* adds important with !, it ovride previous rules on this el */
128 | /*
129 | the z-index of the ApexCharts is 11
130 | we want the z-index of the sidebar higher so that
131 | the charts are not showing over the sidebar
132 | on small screens
133 | */
134 | .sidebar-responsive {
135 | display: inline !important;
136 | position: absolute;
137 | z-index: 12 !important
138 | }
139 |
140 | /* -------- MAIN ----------- */
141 |
142 | .main-container {
143 | grid-area: main;
144 | /* overflow-y: auto; */
145 | padding: 0px 20px 20px 20px;
146 | }
147 |
148 | .main-title {
149 | display: flex;
150 | justify-content: space-between;
151 | }
152 |
153 | .main-title > .timestamp {
154 | flex-direction: row-reverse;
155 | }
156 |
157 | .main-title > p {
158 | font-size: 20px;
159 | }
160 |
161 | /* formats the top cards to be beside each other 4 */
162 | .main-cards {
163 | display: grid;
164 | grid-template-columns: 1fr 1fr 1fr 1fr;
165 | gap: 20px;
166 | /* margin: 20px 0; */
167 | }
168 |
169 | /* format cards: color, bg */
170 | .card {
171 | display: flex;
172 | flex-direction: column;
173 | justify-content: space-around;
174 | padding: 20px;
175 | background-color: #ffffff;
176 | box-sizing: border-box;
177 | border: solid #d2d2d2;
178 | border-radius: 5px;
179 | box-shadow: 0 6px 7px -4px rbga(0, 0, 0, 0.2);
180 | }
181 |
182 | .card:hover {
183 | display: flex;
184 | flex-direction: column;
185 | justify-content: space-around;
186 | padding: 20px;
187 | background-color: #ffffff;
188 | box-sizing: border-box;
189 | border: solid #d2d2d2;
190 | border-radius: 5px;
191 | box-shadow: 0 6px 7px -4px rbga(0, 0, 0, 0.2);
192 | cursor: pointer;
193 | }
194 |
195 | /* use first child selector selects the first card */
196 | .card:first-child {
197 | border-left: 7px solid #246dec;
198 | }
199 |
200 | /* select the 2nd card of the 4, the second within the parent el */
201 | .card:nth-child(2) {
202 | border-left: 7px solid #f5b74f;
203 | }
204 |
205 | .card:nth-child(3) {
206 | border-left: 7px solid #367952;
207 | }
208 |
209 | .card:nth-child(4) {
210 | border-left: 7px solid #cc3c43;
211 | }
212 |
213 | .card > span {
214 | font-size: 20px;
215 | font-weight: 600;
216 | }
217 |
218 | /* card TITLE w/ ICON beside it */
219 | .card-inner {
220 | display: flex;
221 | align-items: center;
222 | justify-content: space-between;
223 | }
224 |
225 | .card-inner > p {
226 | font-size: 18px;
227 | }
228 |
229 | .card-inner > span {
230 | font-size: 35px;
231 | }
232 |
233 | /* -------- MAIN: CHARTS ----------- */
234 |
235 | .charts {
236 | display: grid;
237 | grid-template-columns: 1fr 1fr;
238 | gap: 20px;
239 | }
240 |
241 | .charts-card {
242 | background-color: #ffffff;
243 | margin-bottom: 20px;
244 | padding: 25px;
245 | box-sizing: border-box;
246 | -webkit-column-break-inside: avoid;
247 | border: 1px solid #d2d2d3;
248 | border-radius: 5px;
249 | box-shadow: 0 6px 7px -4px rbga(0, 0, 0, 0.2);
250 | max-height: 550px;
251 | }
252 |
253 | .recommendations {
254 | background-color: #ffffff;
255 | margin-bottom: 20px;
256 | padding: 25px;
257 | box-sizing: border-box;
258 | -webkit-column-break-inside: avoid;
259 | border: 1px solid #d2d2d3;
260 | border-radius: 5px;
261 | box-shadow: 0 6px 7px -4px rbga(0, 0, 0, 0.2);
262 | overflow-y: scroll;
263 | max-height: 550px;
264 | }
265 |
266 | .chart-title {
267 | display: flex;
268 | align-items: center;
269 | justify-content: center;
270 | font-size: 22px;
271 | font-weight: 600;
272 | margin-top: 0px;
273 | }
274 |
275 |
276 | /* ---------- SCROLLBARS ---------- */
277 |
278 | ::-webkit-scrollbar {
279 | width: 5px;
280 | height: 6px;
281 | }
282 |
283 | ::-webkit-scrollbar-track {
284 | box-shadow: inset 0 0 5px #a5aaad;
285 | border-radius: 10px;
286 | }
287 |
288 | ::-webkit-scrollbar-thumb {
289 | background-color: #4f35a1;
290 | border-radius: 10px;
291 | }
292 |
293 | ::-webkit-scrollbar-thumb:hover {
294 | background-color: #a5aaad;
295 | }
296 |
297 |
298 | /* ---------- MEDIA QUERIES ---------- */
299 |
300 |
301 | /* Medium <= 992px */
302 | @media screen and (max-width: 992px) {
303 | .grid-container {
304 | grid-template-columns: 1fr;
305 | grid-template-rows: 0.2fr 3fr;
306 | grid-template-areas:
307 | "header"
308 | "main";
309 | }
310 |
311 | #sidebar {
312 | display: none;
313 | }
314 |
315 | .menu-icon {
316 | display: inline;
317 | }
318 |
319 | .sidebar-title > span {
320 | display: inline;
321 | }
322 | }
323 |
324 | /* Small <= 768px */
325 | @media screen and (max-width: 768px) {
326 | .main-cards {
327 | grid-template-columns: 1fr;
328 | gap: 10px;
329 | margin-bottom: 0;
330 | }
331 |
332 | .charts {
333 | grid-template-columns: 1fr;
334 | margin-top: 30px;
335 | }
336 | }
337 |
338 | /* Extra Small <= 576px */
339 | @media screen and (max-width: 576px) {
340 | .header-left {
341 | display: none;
342 | }
343 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strict": true,
6 | "noImplicitReturns": true,
7 | "noImplicitAny": true,
8 | "module": "es6",
9 | "moduleResolution": "node",
10 | "target": "es5",
11 | "allowJs": true,
12 | "jsx": "react",
13 | "resolveJsonModule": true,
14 | "esModuleInterop": true,
15 | },
16 | "include": [
17 | "./src/**/*"
18 | ]
19 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const path = require("path");
3 | const HtmlWebpackPlugin = require("html-webpack-plugin");
4 |
5 | const config = {
6 | entry: "./src/index.tsx",
7 | output: {
8 | path: path.resolve(__dirname, './astrospeed'),
9 | filename: 'bundle.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.(js|jsx)$/,
15 | use: "babel-loader",
16 | exclude: [
17 | /node_modules\/(?!astrospeed).*/
18 | ]
19 | },
20 | {
21 | test: /\.css$/,
22 | use: ["style-loader", "css-loader"],
23 | },
24 | {
25 | test: /\.ts(x)?$/,
26 | loader: "ts-loader",
27 | exclude: [
28 | /node_modules\/(?!astrospeed).*/
29 | ]
30 | },
31 | { test: /\.svg$/, use: ["@svgr/webpack", "url-loader"] },
32 | ],
33 | },
34 | plugins: [
35 | new HtmlWebpackPlugin({
36 | templateContent: ({ htmlWebpackPlugin }) =>
37 | 'astroSpeed Report
',
38 | filename: "index.html",
39 | chunks: 'bundle'
40 | }),
41 | ],
42 | resolve: {
43 | extensions: [".tsx", ".ts", ".js", ".jsx"],
44 | }
45 | };
46 |
47 | module.exports = config;
48 |
--------------------------------------------------------------------------------
/website/.npmrc:
--------------------------------------------------------------------------------
1 | # Expose Astro dependencies for `pnpm` users
2 | shamefully-hoist=true
3 |
--------------------------------------------------------------------------------
/website/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config';
2 | import tailwind from '@astrojs/tailwind';
3 |
4 | // https://astro.build/config
5 | export default defineConfig({
6 | integrations: [tailwind()],
7 | });
8 |
--------------------------------------------------------------------------------
/website/demo/index.html:
--------------------------------------------------------------------------------
1 | astroSpeed Report
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astrospeed",
3 | "type": "commonjs",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "dev": "astro dev",
8 | "start": "astro dev",
9 | "build": "astro build",
10 | "preview": "astro preview",
11 | "astro": "astro"
12 | },
13 | "dependencies": {
14 | "@astrojs/tailwind": "^2.0.1",
15 | "astro": "^1.2.8",
16 | "express": "^4.18.1",
17 | "postcss": "^8.4.17",
18 | "tailwindcss": "^3.1.8"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/website/public/Audrey-Park.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/Audrey-Park.webp
--------------------------------------------------------------------------------
/website/public/How-it-works-chart.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/How-it-works-chart.webp
--------------------------------------------------------------------------------
/website/public/Luke-McInerney.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/Luke-McInerney.webp
--------------------------------------------------------------------------------
/website/public/Marco-Gonzalez.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/Marco-Gonzalez.webp
--------------------------------------------------------------------------------
/website/public/Rami-Abdelghafar.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/Rami-Abdelghafar.webp
--------------------------------------------------------------------------------
/website/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/website/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/website/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/website/public/astronaut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/astronaut.png
--------------------------------------------------------------------------------
/website/public/colored-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/colored-logo.png
--------------------------------------------------------------------------------
/website/public/crater-footer.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/crater-footer.webp
--------------------------------------------------------------------------------
/website/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/favicon-16x16.png
--------------------------------------------------------------------------------
/website/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/favicon-32x32.png
--------------------------------------------------------------------------------
/website/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/favicon.ico
--------------------------------------------------------------------------------
/website/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/website/public/moonTelescope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/moonTelescope.png
--------------------------------------------------------------------------------
/website/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/website/public/space-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/space-bg.png
--------------------------------------------------------------------------------
/website/public/tether.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/tether.png
--------------------------------------------------------------------------------
/website/public/white-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/astrospeed/be62f71f469b34cf4e37df399224c0ffc9583591/website/public/white-logo.png
--------------------------------------------------------------------------------
/website/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const PORT = 80;
4 |
5 | app.use('/demo', express.static('demo'))
6 | app.use(express.static('dist'));
7 |
8 |
9 | app.listen(PORT);
--------------------------------------------------------------------------------
/website/src/components/Astronaut.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 |
4 |
5 | ---
6 |
7 |
13 |
14 |
16 | We believe that with a great developer experience, comes a great user experience. Combining the blazingly fast runtime of AstroJS and industry-standard metrics of Google Lighthouse, we created an industry first, astroSpeed.
17 |
18 |
--------------------------------------------------------------------------------
/website/src/components/Button.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const {color} = Astro.props;
3 | let tailClass;
4 | if (color === 'stone') tailClass = `bg-stone-100 hover:bg-stone-300 transition ease-in-out delay-50 hover:-translate-y-1 hover:scale-110 w-48 h-10 py-2 px-2 m-1 font-semibold rounded-lg`;
5 | if (color === 'yellow') tailClass = `bg-yellow-200 hover:bg-yellow-400 transition ease-in-out delay-50 hover:-translate-y-1 hover:scale-110 w-48 h-10 py-2 px-2 m-1 font-semibold rounded-lg`;
6 | ---
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/website/src/components/Comparisons.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 |
4 |
5 | ---
6 |
7 |
8 |
18 |
19 |
20 |
COMPARE YOUR CODE PERFORMANCE
21 |
22 |
Without
23 |
vs.
24 |
astroSpeed
25 |
26 |
X
27 |
Only see metrics after your site is published
28 |
✓
29 |
On-demand performance & SEO metrics
30 |
31 |
X
32 |
Forced to wait
33 |
✓
34 |
Pivot faster with early insight
35 |
36 |
X
37 |
Low visibility to where your code needs improvement
38 |
✓
39 |
Pinpoint least optimal code as you build
40 |
41 |
X
42 |
Time lost scanning pages of code
43 |
✓
44 |
More control & visibility
45 |
46 |
47 |
--------------------------------------------------------------------------------
/website/src/components/ContactCard.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 | const {name, github, linkedin, picture} = Astro.props;
4 |
5 | ---
6 |
7 |
81 |
82 |
83 |
84 |
85 |
{name}
86 |
Software Engineer
87 |
91 |
92 |
--------------------------------------------------------------------------------
/website/src/components/ContactUs.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 | import ContactCard from "./ContactCard.astro";
4 |
5 | ---
6 |
7 |
8 |
17 |
18 |
19 |
25 |
31 |
37 |
43 |
44 |
--------------------------------------------------------------------------------
/website/src/components/NavBar.astro:
--------------------------------------------------------------------------------
1 | ---
2 | // Component Imports
3 | import Button from './Button.astro';
4 | ---
5 |
6 |
--------------------------------------------------------------------------------
/website/src/components/RocketChart.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 |
4 |
5 | ---
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/website/src/components/SplashPoster.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 |
4 |
5 | ---
6 |
7 |
109 |
110 |
111 |
112 |
113 |
astroSpeed
114 |
astro projects deserve performance metrics, on-demand
115 |
116 |
117 |
--------------------------------------------------------------------------------
/website/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/website/src/layouts/main.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import NavBar from '../components/NavBar.astro';
3 | ---
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
30 | astroSpeed
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/website/src/pages/getstarted.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 | import Main from '../layouts/main.astro';
4 |
5 | ---
6 |
7 |
8 | Get Started Content Goes Here
9 |
--------------------------------------------------------------------------------
/website/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | // Component Imports
3 | import Main from '../layouts/main.astro';
4 | import SplashPoster from '../components/SplashPoster.astro';
5 | import Comparisons from '../components/Comparisons.astro';
6 | import ContactUs from '../components/ContactUs.astro';
7 | import RocketChart from '../components/RocketChart.astro';
8 | import Astronaut from '../components/Astronaut.astro';
9 |
10 | // Full Astro Component Syntax:
11 | // https://docs.astro.build/core-concepts/astro-components/
12 | ---
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/website/src/pages/login.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 | import Main from '../layouts/main.astro';
4 |
5 | ---
6 |
7 |
8 | Coming soon...
9 |
--------------------------------------------------------------------------------
/website/src/pages/watchdemo.astro:
--------------------------------------------------------------------------------
1 | ---
2 | //Component Script Block
3 | import Main from '../layouts/main.astro';
4 |
5 | ---
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/website/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
3 | plugins: [],
4 | };
5 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/base"
3 | }
4 |
--------------------------------------------------------------------------------