use commit hash as assets' version instead of dedicated version per file

This commit is contained in:
E. S. 2025-05-02 22:40:40 +03:00
parent 93d0b6d09c
commit 1f2e75e6f4
7 changed files with 54 additions and 64 deletions

2
.gitignore vendored
View File

@ -7,7 +7,7 @@ src/test.php
.DS_Store .DS_Store
._.DS_Store ._.DS_Store
.sass-cache/ .sass-cache/
config-static.php config-runtime.php
/config.yaml /config.yaml
/.idea /.idea
/htdocs/dist-css /htdocs/dist-css

View File

@ -10,7 +10,7 @@ all:
deploy: deploy:
./deploy/deploy.sh ./deploy/deploy.sh
static: build-js build-css static-config static: build-js build-css runtime-config
build-js: build-js:
./deploy/build_js.sh -i ./htdocs/js -o ./htdocs/dist-js ./deploy/build_js.sh -i ./htdocs/js -o ./htdocs/dist-js
@ -18,5 +18,5 @@ build-js:
build-css: build-css:
./deploy/build_css.sh -i ./htdocs/scss -o ./htdocs/dist-css ./deploy/build_css.sh -i ./htdocs/scss -o ./htdocs/dist-css
static-config: runtime-config:
./deploy/gen_static_config.php -i ./htdocs > ./config-static.php ./deploy/gen_runtime_config.php -i ./htdocs > ./config-runtime.php

View File

@ -41,7 +41,9 @@ cp "$DEV_DIR/config.yaml" .
"$DIR"/build_js.sh -i "$DEV_DIR/htdocs/js" -o "$STAGING_DIR/htdocs/dist-js" || die "build_js failed" "$DIR"/build_js.sh -i "$DEV_DIR/htdocs/js" -o "$STAGING_DIR/htdocs/dist-js" || die "build_js failed"
"$DIR"/build_css.sh -i "$DEV_DIR/htdocs/scss" -o "$STAGING_DIR/htdocs/dist-css" || die "build_css failed" "$DIR"/build_css.sh -i "$DEV_DIR/htdocs/scss" -o "$STAGING_DIR/htdocs/dist-css" || die "build_css failed"
$PHP "$DIR"/gen_static_config.php -i "$STAGING_DIR/htdocs" > "$STAGING_DIR/config-static.php" || die "gen_static_config failed" $PHP "$DIR"/gen_runtime_config.php \
--htdocs-dir "$STAGING_DIR/htdocs" \
--commit-hash "$(git rev-parse --short=8 HEAD)" > "$STAGING_DIR/config-runtime.php" || die "gen_runtime_config failed"
cd "$DIR" cd "$DIR"

View File

@ -1,67 +1,57 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
use app\CliUtil;
require __DIR__.'/../src/init.php'; require __DIR__.'/../src/init.php';
if ($argc <= 1) { $htdocs_dir = null;
usage(); $commit_hash = null;
exit(1); for ($i = 1; $i < $argc; $i++) {
} switch ($argv[$i]) {
case '--htdocs-dir':
$htdocs_dir = $argv[++$i] ?? usage('missing value for --htdocs-dir');
break;
$input_dir = null; case '--commit-hash':
$commit_hash = $argv[++$i] ?? usage('missing value for --commit-hash');
array_shift($argv);
while (count($argv) > 0) {
switch ($argv[0]) {
case '-i':
array_shift($argv);
$input_dir = array_shift($argv);
break; break;
default: default:
CliUtil::die('unsupported argument: '.$argv[0]); usage("unknown option {$argv[$i]}");
} }
} }
if (is_null($input_dir)) if (is_null($htdocs_dir) || is_null($commit_hash))
CliUtil::die("input directory has not been specified"); usage();
$hashes = []; $hashes = [
'commit_hash' => $commit_hash,
'assets' => []
];
foreach (['css', 'js'] as $type) { foreach (['css', 'js'] as $type) {
$entries = glob_recursive($input_dir.'/dist-'.$type.'/*.'.$type); $entries = glob_recursive($htdocs_dir.'/dist-'.$type.'/*.'.$type);
if (empty($entries)) { if (empty($entries)) {
CliUtil::error("warning: no files found in $input_dir/dist-$type"); fwrite(STDERR, "warning: no files found in $htdocs_dir/dist-$type\n");
continue; continue;
} }
foreach ($entries as $file) { foreach ($entries as $file) {
$hashes[$type.'/'.basename($file)] = [ $hashes['assets'][$type.'/'.basename($file)] = [
'version' => get_hash($file),
'integrity' => [] 'integrity' => []
]; ];
foreach (\engine\skin\FeaturedSkin::RESOURCE_INTEGRITY_HASHES as $hash_type) foreach (\engine\skin\FeaturedSkin::RESOURCE_INTEGRITY_HASHES as $hash_type)
$hashes[$type.'/'.basename($file)]['integrity'][$hash_type] = base64_encode(hash_file($hash_type, $file, true)); $hashes['assets'][$type.'/'.basename($file)]['integrity'][$hash_type] = base64_encode(hash_file($hash_type, $file, true));
} }
} }
echo "<?php\n\n"; echo "<?php\n\n";
echo "return ".var_export($hashes, true).";\n"; echo "return ".var_export($hashes, true).";\n";
function usage(): void { function usage(string $msg = ''): never {
global $argv; if ($msg !== '')
echo <<<EOF fwrite(STDERR, "error: {$msg}\n");
usage: {$argv[0]} [OPTIONS] $script = $GLOBALS['argv'][0];
fwrite(STDERR, "usage: {$script} --htdocs_dir DIR --commit-hash HASH\n");
Options: exit(1);
-i input htdocs directory
EOF;
}
function get_hash(string $path): string {
return substr(sha1(file_get_contents($path)), 0, 8);
} }
function glob_escape(string $pattern): string { function glob_escape(string $pattern): string {

View File

@ -10,10 +10,17 @@ var StaticManager = {
versions: {}, versions: {},
/** /**
* @type {string}
*/
commitHash: '',
/**
* @param {string} commitHash
* @param {string[]} loadedStyles * @param {string[]} loadedStyles
* @param {object} versions * @param {object} versions
*/ */
init: function(loadedStyles, versions) { init: function(commitHash, loadedStyles, versions) {
this.commitHash = commitHash;
this.loadedStyles = loadedStyles; this.loadedStyles = loadedStyles;
this.versions = versions; this.versions = versions;
}, },
@ -28,7 +35,7 @@ var StaticManager = {
if (!window.appConfig.devMode) { if (!window.appConfig.devMode) {
if (theme === 'dark') if (theme === 'dark')
name += '_dark'; name += '_dark';
url = '/dist-css/'+name+'.css?v='+this.versions.css[name].version; url = '/dist-css/'+name+'.css?v='+this.commitHash;
id = 'style_'+name; id = 'style_'+name;
} else { } else {
url = '/sass.php?name='+name+'&theme='+theme+'&v='+timestamp(); url = '/sass.php?name='+name+'&theme='+theme+'&v='+timestamp();

View File

@ -205,13 +205,13 @@ HTML;
$versions = '{}'; $versions = '{}';
else { else {
$versions = []; $versions = [];
foreach ($config['static'] as $name => $v) { foreach ($config['assets'] as $name => $v) {
list($type, $bname) = $this->getStaticNameParts($name); list($type, $bname) = $this->getStaticNameParts($name);
$versions[$type][$bname] = $v; $versions[$type][$bname] = $v;
} }
$versions = jsonEncode($versions); $versions = jsonEncode($versions);
} }
$html .= 'StaticManager.init('.jsonEncode($this->styleNames).', '.$versions.');'; $html .= 'StaticManager.init(\''.$config['commit_hash'].'\', '.jsonEncode($this->styleNames).', '.$versions.');';
$html .= 'ThemeSwitcher.init();'; $html .= 'ThemeSwitcher.init();';
if (!empty($this->exportedStrings)) { if (!empty($this->exportedStrings)) {
@ -230,16 +230,18 @@ HTML;
} }
protected function jsLink(string $name): string { protected function jsLink(string $name): string {
global $config;
list (, $bname) = $this->getStaticNameParts($name); list (, $bname) = $this->getStaticNameParts($name);
if (isDev()) { if (isDev()) {
$href = '/js.php?name='.urlencode($bname).'&amp;v='.time(); $href = '/js.php?name='.urlencode($bname).'&amp;v='.time();
} else { } else {
$href = '/dist-js/'.$bname.'.js?v='.$this->getStaticVersion($name); $href = '/dist-js/'.$bname.'.js?v='.$config['commit_hash'];
} }
return '<script src="'.$href.'" type="text/javascript"'.$this->getStaticIntegrityAttribute($name).'></script>'; return '<script src="'.$href.'" type="text/javascript"'.$this->getStaticIntegrityAttribute($name).'></script>';
} }
protected function cssLink(string $name, string $theme, &$bname = null): string { protected function cssLink(string $name, string $theme, &$bname = null): string {
global $config;
list(, $bname) = $this->getStaticNameParts($name); list(, $bname) = $this->getStaticNameParts($name);
$config_name = 'css/'.$bname.($theme == 'dark' ? '_dark' : '').'.css'; $config_name = 'css/'.$bname.($theme == 'dark' ? '_dark' : '').'.css';
@ -247,8 +249,7 @@ HTML;
if (isDev()) { if (isDev()) {
$href = '/sass.php?name='.urlencode($bname).'&amp;theme='.$theme.'&amp;v='.time(); $href = '/sass.php?name='.urlencode($bname).'&amp;theme='.$theme.'&amp;v='.time();
} else { } else {
$version = $this->getStaticVersion($config_name); $href = '/dist-css/'.$bname.($theme == 'dark' ? '_dark' : '').'.css?v='.$config['commit_hash'];
$href = '/dist-css/'.$bname.($theme == 'dark' ? '_dark' : '').'.css?v='.$version;
} }
$id = 'style_'.$bname; $id = 'style_'.$bname;
@ -259,7 +260,8 @@ HTML;
} }
protected function cssPrefetchLink(string $name): string { protected function cssPrefetchLink(string $name): string {
$url = '/dist-css/'.$name.'.css?v='.$this->getStaticVersion('css/'.$name.'.css'); global $config;
$url = '/dist-css/'.$name.'.css?v='.$config['commit_hash'];
$integrity = $this->getStaticIntegrityAttribute('css/'.$name.'.css'); $integrity = $this->getStaticIntegrityAttribute('css/'.$name.'.css');
return '<link rel="prefetch" href="'.$url.'"'.$integrity.' />'; return '<link rel="prefetch" href="'.$url.'"'.$integrity.' />';
} }
@ -276,21 +278,10 @@ HTML;
return [$dname, $bname, $ext]; return [$dname, $bname, $ext];
} }
protected function getStaticVersion(string $name): string {
global $config;
if (isDev())
return time();
if (str_starts_with($name, '/')) {
logWarning(__FUNCTION__.': '.$name.' starts with /');
$name = substr($name, 1);
}
return $config['static'][$name]['version'] ?? 'notfound';
}
protected function getStaticIntegrityAttribute(string $name): string { protected function getStaticIntegrityAttribute(string $name): string {
if (isDev()) if (isDev())
return ''; return '';
global $config; global $config;
return ' integrity="'.implode(' ', array_map(fn($hash_type) => $hash_type.'-'.$config['static'][$name]['integrity'][$hash_type], self::RESOURCE_INTEGRITY_HASHES)).'"'; return ' integrity="'.implode(' ', array_map(fn($hash_type) => $hash_type.'-'.$config['assets'][$name]['integrity'][$hash_type], self::RESOURCE_INTEGRITY_HASHES)).'"';
} }
} }

View File

@ -57,10 +57,10 @@ $globalContext->setLogger($logger);
unset($logger); unset($logger);
if (!isDev()) { if (!isDev()) {
if (file_exists(APP_ROOT.'/config-static.php')) if (file_exists(APP_ROOT.'/config-runtime.php'))
$config['static'] = require_once 'config-static.php'; $config += require_once 'config-runtime.php';
else else
die('config-static.php not found'); die('config-runtime.php not found');
// turn off errors output on production domains // turn off errors output on production domains
error_reporting(0); error_reporting(0);