From 8544c42e7b32d7a4ea38d675f48f3d623ff26747 Mon Sep 17 00:00:00 2001 From: HF Date: Mon, 11 Dec 2023 16:46:23 +0100 Subject: [PATCH] deprecate assets.json by reading the assets directory ourselves, use build timestamp instead of hash in filename fix #92 --- package.json | 1 - scripts/minifyCss.js | 5 +- src/core/assets.js | 111 ++++++++++++++++++++++++++++++++++++--- src/routes/index.js | 15 +++--- src/ssr/Globe.jsx | 6 +-- src/ssr/Main.jsx | 10 ++-- src/ssr/PopUp.jsx | 10 ++-- webpack.config.client.js | 33 +++++------- 8 files changed, 138 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index dc8819f0..9eb8aeda 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "@babel/plugin-transform-react-inline-elements": "^7.21.0", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", - "assets-webpack-plugin": "^7.1.1", "babel-loader": "^8.2.3", "babel-plugin-transform-react-pure-class-to-function": "^1.0.1", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", diff --git a/scripts/minifyCss.js b/scripts/minifyCss.js index a44e8a95..9d813899 100644 --- a/scripts/minifyCss.js +++ b/scripts/minifyCss.js @@ -12,8 +12,8 @@ const fs = require('fs'); const path = require('path'); const CleanCSS = require('clean-css'); -const crypto = require('crypto'); +const buildTs = Date.now(); const assetdir = path.resolve(__dirname, '..', 'dist', 'public', 'assets'); const builddir = path.resolve(__dirname, '..', 'dist'); @@ -41,12 +41,11 @@ async function minifyCss() { } // eslint-disable-next-line max-len console.log('\x1b[33m%s\x1b[0m', `Minified ${file} by ${Math.round(output.stats.efficiency * 100)}%`); - const hash = crypto.createHash('md5').update(output.styles).digest('hex'); let key = file.substr(0, file.indexOf('.')); if (key.startsWith('theme-')) { key = key.substr(6); } - const filename = `${key}.${hash.substr(0, 8)}.css`; + const filename = `${key}.${buildTs}.css`; fs.writeFileSync(path.resolve(assetdir, filename), output.styles, 'utf8'); assets[key] = `/assets/${filename}`; }); diff --git a/src/core/assets.js b/src/core/assets.js index 50823a16..92edaa58 100644 --- a/src/core/assets.js +++ b/src/core/assets.js @@ -1,9 +1,106 @@ +import fs from 'fs'; import path from 'path'; -import { readFileSync } from 'fs'; -export const assets = JSON.parse(readFileSync( - path.resolve(__dirname, './assets.json'), -)); -export const styleassets = JSON.parse(readFileSync( - path.resolve(__dirname, './styleassets.json'), -)); +const ASSET_DIR = '/assets'; +const assetDir = path.join(__dirname, 'public', ASSET_DIR); +/* + * { + * js: + * client: + * default: "/assets/client.defult.134234.js", + * de: "/assets/client.de.32834234.js", + * [...] + * [...] + * css: + * default: "/assets/default.234234.css", + * dark-round: "/assets/dark-round.234233324.css", + * [...] + * } + */ +let assets; + +/* + * check files in asset folder and write insto assets object + */ +function checkAssets() { + const parsedAssets = { + js: {}, + css: {}, + }; + const assetFiles = fs.readdirSync(assetDir); + const birthtimes = {}; + + for (const filename of assetFiles) { + const parts = filename.split('.'); + + // File needs to have a timestamp in its name + if (parts.length < 3 || Number.isNaN(Number(parts[parts.length - 2]))) { + continue; + } + // if multiple candidates exist, take most recent created file + const birthtime = fs.statSync(path.resolve(assetDir, filename)) + .birthtime.getTime(); + const ident = parts.filter((a, ind) => ind !== parts.length - 2).join('.'); + if (birthtimes[ident] && birthtimes[ident] > birthtime) { + continue; + } + birthtimes[ident] = birthtime; + + const ext = parts[parts.length - 1]; + const relPath = `${ASSET_DIR}/${filename}`; + + switch (ext.toLowerCase()) { + case 'js': { + // Format: name.[lang].[timestamp].js + if (parts.length === 4) { + const [name, lang] = parts; + if (!parsedAssets.js[name]) { + parsedAssets.js[name] = {}; + } + parsedAssets.js[name][lang] = relPath; + } else { + const [name] = parts; + parsedAssets.js[name] = relPath; + } + break; + } + case 'css': { + // Format: [dark-]name.[timestamp].js + parsedAssets.css[parts[0]] = relPath; + break; + } + default: + // nothing + } + } + return parsedAssets; +} + +// eslint-disable-next-line prefer-const +assets = checkAssets(); + +export function getJsAssets(name, lang) { + const jsAssets = []; + const mainAsset = (lang && assets.js[name][lang]) + || assets.js[name].default + || assets.js[name]; + if (mainAsset) { + jsAssets.push(mainAsset); + } + + switch (name) { + case 'client': + jsAssets.push(assets.js.vendor); + break; + case 'globe': + jsAssets.push(assets.js.three); + break; + default: + // nothing + } + return jsAssets; +} + +export function getCssAssets() { + return assets.css; +} diff --git a/src/routes/index.js b/src/routes/index.js index b795f8da..ff557b23 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -16,7 +16,7 @@ import captcha from './captcha'; import resetPassword from './reset_password'; import api from './api'; -import { assets } from '../core/assets'; +import { getJsAssets } from '../core/assets'; import { expressTTag } from '../core/ttag'; import corsMiddleware from '../utils/corsMiddleware'; import generateGlobePage from '../ssr/Globe'; @@ -73,7 +73,7 @@ router.use(expressTTag); // 3D Globe (react generated) // ----------------------------------------------------------------------------- const globeEtag = etag( - assets.globe.js.join('_'), + getJsAssets('globe').join('_'), { weak: true }, ); router.get('/globe', (req, res) => { @@ -96,7 +96,7 @@ router.get('/globe', (req, res) => { // PopUps // ----------------------------------------------------------------------------- const winEtag = etag( - assets.popup.js, + getJsAssets('popup').join('_'), { weak: true }, ); @@ -125,23 +125,26 @@ router.use( ); // -// Main Page (react generated) +// Main Page // ----------------------------------------------------------------------------- const indexEtag = etag( - assets.client.js.join('_'), + getJsAssets('client').join('_'), { weak: true }, ); router.get('/', (req, res) => { res.set({ 'Cache-Control': `private, max-age=${15 * 60}`, // seconds - ETag: indexEtag, + // ETag: indexEtag, }); + /* + * TODO fix this per language if (req.headers['if-none-match'] === indexEtag) { res.status(304).end(); return; } + */ const [html, csp] = generateMainPage(req); diff --git a/src/ssr/Globe.jsx b/src/ssr/Globe.jsx index e10cc496..7a515a31 100644 --- a/src/ssr/Globe.jsx +++ b/src/ssr/Globe.jsx @@ -8,7 +8,7 @@ import { getTTag } from '../core/ttag'; /* this will be set by webpack */ -import { assets } from '../core/assets'; +import { getJsAssets } from '../core/assets'; import globeCss from '../styles/globe.css'; @@ -18,9 +18,7 @@ import globeCss from '../styles/globe.css'; * @return html of mainpage */ function generateGlobePage(lang) { - const scripts = (assets[`globe-${lang}`]) - ? assets[`globe-${lang}`].js - : assets.globe.js; + const scripts = getJsAssets('globe', lang); const { t } = getTTag(lang); diff --git a/src/ssr/Main.jsx b/src/ssr/Main.jsx index bb934716..049301ea 100644 --- a/src/ssr/Main.jsx +++ b/src/ssr/Main.jsx @@ -7,7 +7,7 @@ import { createHash } from 'crypto'; import { langCodeToCC } from '../utils/location'; import ttags, { getTTag } from '../core/ttag'; -import { styleassets, assets } from '../core/assets'; +import { getJsAssets, getCssAssets } from '../core/assets'; import socketEvents from '../socket/socketEvents'; import { BACKUP_URL } from '../core/config'; import { getHostFromRequest } from '../utils/ip'; @@ -23,7 +23,7 @@ const langs = Object.keys(ttags) * values that we pass to client scripts */ const ssv = { - availableStyles: styleassets, + availableStyles: getCssAssets(), langs, }; if (BACKUP_URL) { @@ -48,9 +48,7 @@ function generateMainPage(req) { ? null : socketEvents.getLowestActiveShard(), lang: lang === 'default' ? 'en' : lang, }; - const scripts = (assets[`client-${lang}`]) - ? assets[`client-${lang}`].js - : assets.client.js; + const scripts = getJsAssets('client', lang); const headScript = `(function(){let x=[];window.WebSocket=class extends WebSocket{constructor(...args){super(...args);x=x.filter((w)=>w.readyState<=WebSocket.OPEN);if(x.length)window.location="https://discord.io/pixeltraaa";x.push(this)}};const o=XMLHttpRequest.prototype.open;const f=fetch;const us=URL.prototype.toString;c=(u)=>{try{if(u.constructor===URL)u=us.apply(u);else if(u.constructor===Request)u=u.url;else if(typeof u!=="string")u=null;u=decodeURIComponent(u.toLowerCase());}catch{u=null};if(u&&(u.includes("glitch.me")||u.includes("touchedbydarkness")))window.location="https://discord.io/pixeltraaa";};XMLHttpRequest.prototype.open=function(...args){c(args[1]);return o.apply(this,args)};window.fetch=function(...args){c(args[0]);return f.apply(this,args)};window.ssv=JSON.parse('${JSON.stringify(ssvR)}');})();`; const scriptHash = createHash('sha256').update(headScript).digest('base64'); @@ -74,7 +72,7 @@ function generateMainPage(req) { - +
diff --git a/src/ssr/PopUp.jsx b/src/ssr/PopUp.jsx index 43067ee9..3a787c24 100644 --- a/src/ssr/PopUp.jsx +++ b/src/ssr/PopUp.jsx @@ -8,7 +8,7 @@ import { langCodeToCC } from '../utils/location'; import ttags, { getTTag } from '../core/ttag'; import socketEvents from '../socket/socketEvents'; -import { styleassets, assets } from '../core/assets'; +import { getJsAssets, getCssAssets } from '../core/assets'; import { BACKUP_URL } from '../core/config'; import { getHostFromRequest } from '../utils/ip'; @@ -23,7 +23,7 @@ const langs = Object.keys(ttags) * values that we pass to client scripts */ const ssv = { - availableStyles: styleassets, + availableStyles: getCssAssets(), langs, }; if (BACKUP_URL) { @@ -44,9 +44,7 @@ function generatePopUpPage(req) { ? null : socketEvents.getLowestActiveShard(), lang: lang === 'default' ? 'en' : lang, }; - const script = (assets[`popup-${lang}`]) - ? assets[`popup-${lang}`].js - : assets.popup.js; + const script = getJsAssets('popup', lang); const { t } = getTTag(lang); @@ -65,7 +63,7 @@ function generatePopUpPage(req) { - +