diff --git a/src/Plugins/Tia/ContentHash.php b/src/Plugins/Tia/ContentHash.php index 3c56b6cd..368b1209 100644 --- a/src/Plugins/Tia/ContentHash.php +++ b/src/Plugins/Tia/ContentHash.php @@ -60,6 +60,12 @@ final class ContentHash return self::hashPhpContent($raw); } + foreach (['.vue', '.tsx', '.jsx', '.svelte', '.ts', '.js', '.mjs', '.cjs', '.mts'] as $extension) { + if (str_ends_with($lower, $extension)) { + return self::hashJsContent($raw); + } + } + return hash('xxh128', $raw); } @@ -115,4 +121,24 @@ final class ContentHash return hash('xxh128', trim($stripped)); } + + /** + * Conservative JS/TS/Vue/Svelte normaliser. Strips `//` line + * comments and `/* … *\/` block comments that appear on their own + * lines (including leading indentation), then collapses + * whitespace. Deliberately leaves trailing comments after code + * alone — a string literal like `'http://foo'` would be unsafe to + * split on `//` without a full lexer. The direction of error is + * over-detection (we may not strip a trailing comment that's + * purely cosmetic), never under-detection. Blank lines and + * indentation changes are erased regardless. + */ + private static function hashJsContent(string $raw): string + { + $stripped = preg_replace('/^\s*\/\/[^\n]*$/m', '', $raw) ?? $raw; + $stripped = preg_replace('/^\s*\/\*.*?\*\/\s*$/sm', '', $stripped) ?? $stripped; + $stripped = preg_replace('/\s+/', ' ', $stripped) ?? $stripped; + + return hash('xxh128', trim($stripped)); + } } diff --git a/src/Plugins/Tia/Fingerprint.php b/src/Plugins/Tia/Fingerprint.php index aa53eee1..e02e0b44 100644 --- a/src/Plugins/Tia/Fingerprint.php +++ b/src/Plugins/Tia/Fingerprint.php @@ -52,7 +52,15 @@ final readonly class Fingerprint // the PHP fallback) so shared components / layouts / // composables invalidate the specific pages they're used // by, not every browser test. - private const int SCHEMA_VERSION = 8; + // v9: `ContentHash` now normalises JS/TS/Vue/Svelte comments + + // whitespace. Old graphs' run-tree hashes for those files + // were raw-byte; mixing formats would flag every JS file as + // changed on first run. + // v10: `vite.config.*` hashed into the structural bucket. A + // Vite config change reshapes the module dependency graph + // that `JsModuleGraph` records; without a graph rebuild + // the stored `$jsFileToComponents` map silently goes stale. + private const int SCHEMA_VERSION = 10; /** * @return array{