Add various performance improvements

This commit is contained in:
2026-02-14 08:44:40 -05:00
parent 3c93910723
commit 1e69b2b57e
12 changed files with 209 additions and 114 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ backend/bin/
.vscode
*.pem
.env
.direnv
.envrc.local

View File

@@ -15,7 +15,7 @@ help:
## backend: Build and run the backend from source
backend:
@cd backend && go run .
@cd backend && CGO_ENABLED=0 go run .
.PHONY: backend
## frontend: Build and run the frontend from source

View File

@@ -1,6 +1,6 @@
module github.com/davegallant/rfd-fyi
go 1.18
go 1.26
require (
github.com/dlclark/regexp2 v1.11.5

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1771008912,
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

51
flake.nix Normal file
View File

@@ -0,0 +1,51 @@
{
description = "rfd-fyi development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# Node.js LTS with npm
nodejs_20
# Go for backend
go
# Build tools
pkg-config
# Development utilities
git
curl
jq
# Optional: for better development experience
gnumake
];
shellHook = ''
echo "🚀 rfd-fyi development environment loaded"
echo "Available commands:"
echo " Frontend: npm install, npm run build, npm run serve"
echo " Backend: cd backend && CGO_ENABLED=0 go run ."
echo ""
echo "Node version: $(node --version)"
echo "npm version: $(npm --version)"
echo "Go version: $(go version)"
echo ""
echo "Tip: Run 'npm install' to install frontend dependencies"
echo "Tip: Vite is available via 'npx vite' or 'npm run build'"
'';
};
}
);
}

View File

@@ -1,17 +1,39 @@
<!DOCTYPE html>
<html lang="" data-bs-theme="dark">
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="description" content="An overlay of rfd deals" />
<link rel="icon" href="<%= BASE_URL %>favicon.png" />
<!-- DNS prefetch for faster Google Fonts resolution -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://fonts.gstatic.com" />
<!-- Preconnect for faster font loading -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="icon" href="/favicon.png" />
<!-- Load Material Symbols font with optimal display settings -->
<link
rel="preload"
as="font"
type="font/woff2"
href="https://fonts.gstatic.com/s/materialsymbolsoutlined/v211/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhQcyWwg.woff2"
crossorigin
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=block"
/>
<title>rfd-fyi - An overlay of hot deals</title>
<!-- Analytics - loaded async/defer so it doesn't block page -->
<script
async
defer
src="https://umami.davegallant.ca/script.js"
data-website-id="59ffe8be-509a-471e-8cd6-a63c5b35b7aa"
@@ -26,6 +48,5 @@
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<!-- built files will be auto injected -->
</body>
</html>

96
package-lock.json generated
View File

@@ -15,10 +15,8 @@
"axios": "^1.12.0",
"core-js": "^3.32.1",
"cssnano": "^7.0.0",
"jquery": "^4.0.0",
"moment": "^2.29.4",
"dayjs": "^1.11.10",
"vue": "^3.5.17",
"vue-github-button": "^3.0.3",
"vue-loading-overlay": "^6.0.3",
"vue-router": "^5.0.0",
"vuetify": "^3.9.6"
@@ -34,7 +32,6 @@
"eslint-plugin-vue": "^9.17.0",
"postcss-cli": "^11.0.0",
"sass-embedded": "^1.89.2",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^31.0.0",
"vite": "^6.3.6",
"vite-plugin-vuetify": "^2.1.1"
@@ -7252,6 +7249,12 @@
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
},
"node_modules/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
@@ -8983,12 +8986,6 @@
"node": ">=6"
}
},
"node_modules/github-buttons": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/github-buttons/-/github-buttons-2.29.1.tgz",
"integrity": "sha512-TV3YgAKda5hPz75n7QXmGCsSzgVya1vvmBieebg3EB5ScmashTZ0FldViG1aU2d4V5rcAGrtQ7k5uAaCo0A4PA==",
"license": "BSD-2-Clause"
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -9870,12 +9867,6 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jquery": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz",
"integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==",
"license": "MIT"
},
"node_modules/js-message": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
@@ -10737,15 +10728,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/mrmime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
@@ -14809,41 +14791,6 @@
"node": ">= 0.8"
}
},
"node_modules/unplugin": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.5.tgz",
"integrity": "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.14.1",
"picomatch": "^4.0.2",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unplugin-fonts": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.4.0.tgz",
"integrity": "sha512-TIJqr5rSlK/+3oL5nnrrEJ+Ty2taQ/bTJY1C5abYnksl553Q3HoHVqS4pnRLDkwpZq8AYqywib3kEVvHH+CtRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-glob": "^3.3.3",
"unplugin": "2.3.5"
},
"peerDependencies": {
"@nuxt/kit": "^3.0.0 || ^4.0.0",
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
}
}
},
"node_modules/unplugin-utils": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
@@ -14971,26 +14918,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/unplugin/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/unplugin/node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"dev": true,
"license": "MIT"
},
"node_modules/upath": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz",
@@ -15328,15 +15255,6 @@
"node": ">=10"
}
},
"node_modules/vue-github-button": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vue-github-button/-/vue-github-button-3.1.3.tgz",
"integrity": "sha512-ZdOnUuYJL/wUsxISsC96TARzCdf1twaWooZoI14+g4RHsJltPY+Agw6Ox6pjuMqMX0uhSK1JOMFUFNCQGlcZGA==",
"license": "BSD-2-Clause",
"dependencies": {
"github-buttons": "^2.22.0"
}
},
"node_modules/vue-hot-reload-api": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",

View File

@@ -16,10 +16,8 @@
"axios": "^1.12.0",
"core-js": "^3.32.1",
"cssnano": "^7.0.0",
"jquery": "^4.0.0",
"moment": "^2.29.4",
"dayjs": "^1.11.10",
"vue": "^3.5.17",
"vue-github-button": "^3.0.3",
"vue-loading-overlay": "^6.0.3",
"vue-router": "^5.0.0",
"vuetify": "^3.9.6"
@@ -35,7 +33,6 @@
"eslint-plugin-vue": "^9.17.0",
"postcss-cli": "^11.0.0",
"sass-embedded": "^1.89.2",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^31.0.0",
"vite": "^6.3.6",
"vite-plugin-vuetify": "^2.1.1"

View File

@@ -1,6 +1,6 @@
<script>
import axios from "axios";
import moment from "moment";
import dayjs from "dayjs";
import Loading from "vue-loading-overlay";
import { install } from "@github/hotkey";
@@ -55,7 +55,7 @@ export default {
computed: {
formatDate() {
return (v) => {
return moment(String(v)).format("hh:mm A z (MM/DD)");
return dayjs(String(v)).format("hh:mm A z (MM/DD)");
};
},
filteredTopics() {

View File

@@ -1,3 +1,26 @@
/* Material Symbols font optimization - prevent flash of unstyled text */
@font-face {
font-family: 'Material Symbols Outlined';
src: url('https://fonts.gstatic.com/s/materialsymbolsoutlined/v211/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhQcyWwg.woff2') format('woff2');
font-weight: 100 700;
font-style: normal;
font-display: block;
}
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}
body {
max-width: 100%;
}

View File

@@ -2,7 +2,6 @@
import Components from "unplugin-vue-components/vite";
import Vue from "@vitejs/plugin-vue";
import Vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
import Fonts from "unplugin-fonts/vite";
// Utilities
import { defineConfig } from "vite";
@@ -14,23 +13,12 @@ export default defineConfig({
Vue({
template: { transformAssetUrls },
}),
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
Vuetify(),
Components(),
Fonts({
fontsource: {
families: [
{
name: "Roboto",
weights: [100, 300, 400, 500, 700, 900],
styles: ["normal", "italic"],
},
],
},
}),
],
optimizeDeps: {
exclude: ["vuetify"],
include: ["axios", "vue-router", "vue-loading-overlay"],
},
define: { "process.env": {} },
resolve: {
@@ -55,4 +43,37 @@ export default defineConfig({
},
},
},
build: {
target: "esnext",
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
},
},
rollupOptions: {
output: {
manualChunks: {
"vuetify": ["vuetify"],
"vendor": ["axios", "dayjs", "vue-router", "vue-loading-overlay"],
},
chunkFileNames: "js/[name].[hash].js",
entryFileNames: "js/[name].[hash].js",
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split(".");
const ext = info[info.length - 1];
if (/png|jpe?g|gif|tiff|bmp|ico/i.test(ext)) {
return `images/[name].[hash][extname]`;
} else if (/woff|woff2|eot|ttf|otf/i.test(ext)) {
return `fonts/[name].[hash][extname]`;
} else if (ext === "css") {
return `css/[name].[hash][extname]`;
}
return `[name].[hash][extname]`;
},
},
},
chunkSizeWarningLimit: 1000,
reportCompressedSize: true,
},
});