deprecate assets.json by reading the assets directory ourselves,

use build timestamp instead of hash in filename
fix #92
This commit is contained in:
HF 2023-12-11 16:46:23 +01:00
parent 72561d9752
commit 8544c42e7b
8 changed files with 138 additions and 53 deletions

View File

@ -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",

View File

@ -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}`;
});

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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) {
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<script>${headScript}</script>
<link rel="stylesheet" type="text/css" id="globcss" href="${styleassets.default}" />
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
</head>
<body>
<div id="app"></div>

View File

@ -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) {
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<script>window.ssv=JSON.parse('${JSON.stringify(ssvR)}')</script>
<link rel="stylesheet" type="text/css" id="globcss" href="${styleassets.default}" />
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
</head>
<body>
<div id="app" class="popup">

View File

@ -6,7 +6,6 @@ const fs = require('fs');
const path = require('path');
const process = require('process');
const webpack = require('webpack');
const AssetsPlugin = require('assets-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
/*
@ -15,16 +14,9 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
process.chdir(__dirname);
/*
* Emit a file with assets paths
* timestamp for filenames
*/
const assetPlugin = new AssetsPlugin({
path: path.resolve('dist'),
filename: 'assets.json',
update: true,
entrypoints: true,
prettyPrint: true,
});
const buildTs = Date.now();
function buildWebpackClientConfig(
development,
@ -66,21 +58,19 @@ function buildWebpackClientConfig(
devtool: (development) ? 'inline-source-map' : false,
entry: {
[(locale !== 'default') ? `client-${locale}` : 'client']:
[`client.${locale}`]:
[path.resolve('src', 'client.js')],
[(locale !== 'default') ? `globe-${locale}` : 'globe']:
[`globe.${locale}`]:
[path.resolve('src', 'globe.js')],
[(locale !== 'default') ? `popup-${locale}` : 'popup']:
[`popup.${locale}`]:
[path.resolve('src', 'popup.js')],
},
output: {
path: path.resolve('dist', 'public', 'assets'),
publicPath: '/assets/',
filename: '[name].[chunkhash:8].js',
chunkFilename: (locale !== 'default')
? `[name]-${locale}.[chunkhash:8].js`
: '[name].[chunkhash:8].js',
// publicPath: '/assets/', // Is this neccessary?
filename: `[name].${buildTs}.js`,
chunkFilename: `[name].${locale}.${buildTs}.js`,
},
resolve: {
@ -148,8 +138,6 @@ function buildWebpackClientConfig(
'process.env.BROWSER': true,
}),
assetPlugin,
// Webpack Bundle Analyzer
// https://github.com/th0r/webpack-bundle-analyzer
...analyze ? [new BundleAnalyzerPlugin({ analyzerPort: 8889 })] : [],
@ -163,6 +151,11 @@ function buildWebpackClientConfig(
default: false,
defaultVendors: false,
/*
* this layout of chunks is also assumed in src/core/assets.js
* client -> client.js + vendor.js
* globe -> globe.js + three.js
*/
vendor: {
name: 'vendor',
chunks: (chunk) => chunk.name.startsWith('client'),