mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
build & use content-hashed theme bundles
This commit is contained in:
parent
33ea81baf9
commit
7afb3ae14a
6 changed files with 102 additions and 27 deletions
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
|
@ -5,6 +5,8 @@
|
|||
/tmp
|
||||
/out-tsc
|
||||
server.run.js
|
||||
.theme-build
|
||||
theme-manifest.json
|
||||
|
||||
# docker
|
||||
Dockerfile
|
||||
|
|
|
|||
|
|
@ -171,25 +171,15 @@
|
|||
"src/robots.txt",
|
||||
"src/config.js",
|
||||
"src/customize.js",
|
||||
"src/config.template.js"
|
||||
"src/config.template.js",
|
||||
{
|
||||
"glob": "*.css",
|
||||
"input": ".theme-build",
|
||||
"output": "/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss",
|
||||
{
|
||||
"input": "src/theme-contrast.scss",
|
||||
"bundleName": "contrast",
|
||||
"inject": false
|
||||
},
|
||||
{
|
||||
"input": "src/theme-softsimon.scss",
|
||||
"bundleName": "softsimon",
|
||||
"inject": false
|
||||
},
|
||||
{
|
||||
"input": "src/theme-bukele.scss",
|
||||
"bundleName": "bukele",
|
||||
"inject": false
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||
],
|
||||
"vendorChunk": true,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,22 @@ try {
|
|||
throw new Error(e);
|
||||
}
|
||||
|
||||
// Inject theme manifest if it exists (created by generate-themes.js)
|
||||
const THEME_MANIFEST_FILE = 'theme-manifest.json';
|
||||
try {
|
||||
const themeManifest = fs.readFileSync(THEME_MANIFEST_FILE, 'utf-8');
|
||||
const themeFiles = JSON.parse(themeManifest);
|
||||
let indexHtml = fs.readFileSync('src/index.html', 'utf-8');
|
||||
const script = ` <script>window.__env=window.__env||{};window.__env.THEME_FILES=${JSON.stringify(themeFiles)};</script>`;
|
||||
indexHtml = indexHtml.replace('</head>', `${script}\n</head>`);
|
||||
fs.writeFileSync('src/index.html', indexHtml);
|
||||
console.log('Injected theme manifest into src/index.html:', themeFiles);
|
||||
} catch (e) {
|
||||
if (e.code !== 'ENOENT') {
|
||||
console.log('Warning: Could not inject theme manifest:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const packageJson = fs.readFileSync('package.json');
|
||||
packetJsonVersion = JSON.parse(packageJson).version;
|
||||
|
|
|
|||
57
frontend/generate-themes.js
Normal file
57
frontend/generate-themes.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const THEMES = ['contrast', 'softsimon', 'bukele'];
|
||||
const STAGING_DIR = path.join(__dirname, '.theme-build');
|
||||
const DIST_DIR = path.join(__dirname, 'dist/mempool/browser');
|
||||
const MANIFEST_FILE = path.join(__dirname, 'theme-manifest.json');
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
if (command === 'copy') {
|
||||
const themeFiles = fs.readdirSync(STAGING_DIR).filter(f => f.endsWith('.css'));
|
||||
for (const dir of fs.readdirSync(DIST_DIR, { withFileTypes: true })) {
|
||||
if (dir.isDirectory()) {
|
||||
for (const file of themeFiles) {
|
||||
fs.copyFileSync(path.join(STAGING_DIR, file), path.join(DIST_DIR, dir.name, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Copied ${themeFiles.length} theme files to all locale directories`);
|
||||
} else {
|
||||
fs.rmSync(STAGING_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(STAGING_DIR, { recursive: true });
|
||||
|
||||
const manifest = {};
|
||||
|
||||
for (const theme of THEMES) {
|
||||
const inputFile = path.join(__dirname, `src/theme-${theme}.scss`);
|
||||
const tempOutput = path.join(STAGING_DIR, `${theme}.tmp.css`);
|
||||
|
||||
try {
|
||||
execSync(`npx sass --style=compressed --no-source-map "${inputFile}" "${tempOutput}"`, {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to compile theme-${theme}.scss:`, e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const css = fs.readFileSync(tempOutput);
|
||||
const hash = crypto.createHash('md5').update(css).digest('hex').slice(0, 16);
|
||||
|
||||
const nonHashedFilename = `${theme}.css`;
|
||||
fs.copyFileSync(tempOutput, path.join(STAGING_DIR, nonHashedFilename));
|
||||
|
||||
const hashedFilename = `${theme}.${hash}.css`;
|
||||
fs.renameSync(tempOutput, path.join(STAGING_DIR, hashedFilename));
|
||||
|
||||
manifest[theme] = hashedFilename;
|
||||
console.log(`Built ${nonHashedFilename} and ${hashedFilename}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
||||
console.log('Theme manifest written to theme-manifest.json');
|
||||
}
|
||||
|
|
@ -24,16 +24,18 @@
|
|||
"tsc": "./node_modules/typescript/bin/tsc",
|
||||
"i18n-extract-from-source": "npm run ng -- extract-i18n --out-file ./src/locale/messages.xlf",
|
||||
"i18n-pull-from-transifex": "tx pull -a --minimum-perc 1 --force",
|
||||
"serve": "npm run generate-config && npm run ng -- serve -c local",
|
||||
"serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod",
|
||||
"serve:parameterized": "npm run generate-config && npm run ng -- serve -c parameterized",
|
||||
"start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local",
|
||||
"start:parameterized": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized",
|
||||
"start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora",
|
||||
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod",
|
||||
"start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed",
|
||||
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets",
|
||||
"sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'",
|
||||
"serve": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c local",
|
||||
"serve:local-prod": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c local-prod",
|
||||
"serve:parameterized": "npm run generate-themes && npm run generate-config && npm run ng -- serve -c parameterized",
|
||||
"start": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local",
|
||||
"start:parameterized": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized",
|
||||
"start:local-esplora": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora",
|
||||
"start:local-prod": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod",
|
||||
"start:mixed": "npm run generate-themes && npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed",
|
||||
"build": "npm run generate-themes && npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets",
|
||||
"generate-themes": "node generate-themes.js",
|
||||
"copy-themes": "node generate-themes.js copy",
|
||||
"sync-assets": "npm run copy-themes && rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'",
|
||||
"sync-assets-dev": "node sync-assets.js 'src/resources/'",
|
||||
"generate-config": "node generate-config.js",
|
||||
"test": "npm run ng -- test",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export class ThemeService {
|
|||
this.themeState$.next({ theme, loading: false });
|
||||
};
|
||||
this.style.onerror = () => this.apply('default');
|
||||
this.style.href = `${theme}.css`;
|
||||
this.style.href = this.getThemeFile(theme);
|
||||
|
||||
if (!this.stateService.env.customize?.theme) {
|
||||
this.storageService.setValue('theme-preference', theme);
|
||||
|
|
@ -78,4 +78,12 @@ export class ThemeService {
|
|||
this.apply('default');
|
||||
}
|
||||
}
|
||||
|
||||
private getThemeFile(theme: string): string {
|
||||
const themeFiles = (window as any).__env?.THEME_FILES;
|
||||
if (themeFiles?.[theme]) {
|
||||
return themeFiles[theme];
|
||||
}
|
||||
return `${theme}.css`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue