Use prebuilt route artifacts during hot reload
This commit is contained in:
+101
-13
@@ -8,21 +8,78 @@ class RouteArtifactState
|
||||
{
|
||||
public function store(string $type, array $artifact): void
|
||||
{
|
||||
$previous = $this->loadPayload($type);
|
||||
$payload = [
|
||||
'timestamp' => time(),
|
||||
'type' => $type,
|
||||
'artifact' => $artifact,
|
||||
'build' => is_array($previous['build'] ?? null) ? $previous['build'] : null,
|
||||
];
|
||||
|
||||
$directory = dirname($this->getFilePath($type));
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($this->getFilePath($type), json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$this->writePayload($type, $payload);
|
||||
}
|
||||
|
||||
public function load(string $type): array
|
||||
{
|
||||
$data = $this->loadPayload($type);
|
||||
return is_array($data['artifact'] ?? null) ? $data['artifact'] : [];
|
||||
}
|
||||
|
||||
public function markBuild(string $type, array $changedFiles): void
|
||||
{
|
||||
$data = $this->loadPayload($type);
|
||||
if (!is_array($data['artifact'] ?? null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['build'] = [
|
||||
'generation' => time(),
|
||||
'timestamp' => time(),
|
||||
'changed_files' => $this->normalizeFiles($changedFiles),
|
||||
];
|
||||
|
||||
$this->writePayload($type, $data);
|
||||
}
|
||||
|
||||
public function clearBuild(string $type): void
|
||||
{
|
||||
$data = $this->loadPayload($type);
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['build'] = null;
|
||||
$this->writePayload($type, $data);
|
||||
}
|
||||
public function coversChangedFiles(string $type, array $changedFiles): bool
|
||||
{
|
||||
if (empty($changedFiles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->loadPayload($type);
|
||||
$build = is_array($data['build'] ?? null) ? $data['build'] : [];
|
||||
$builtFiles = is_array($build['changed_files'] ?? null) ? $build['changed_files'] : [];
|
||||
if (empty($builtFiles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$built = array_fill_keys($this->normalizeFiles($builtFiles), true);
|
||||
foreach ($this->normalizeFiles($changedFiles) as $file) {
|
||||
if (!isset($built[$file])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(string $type): bool
|
||||
{
|
||||
return file_exists($this->getFilePath($type));
|
||||
}
|
||||
|
||||
private function loadPayload(string $type): array
|
||||
{
|
||||
$file = $this->getFilePath($type);
|
||||
if (!file_exists($file)) {
|
||||
@@ -30,16 +87,47 @@ class RouteArtifactState
|
||||
}
|
||||
|
||||
$data = json_decode((string)file_get_contents($file), true);
|
||||
if (!is_array($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return is_array($data['artifact'] ?? null) ? $data['artifact'] : [];
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
public function has(string $type): bool
|
||||
private function writePayload(string $type, array $payload): void
|
||||
{
|
||||
return file_exists($this->getFilePath($type));
|
||||
$file = $this->getFilePath($type);
|
||||
$directory = dirname($file);
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
$tmpFile = $file . '.tmp.' . getmypid();
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
if (file_put_contents($tmpFile, $json, LOCK_EX) === false) {
|
||||
throw new \RuntimeException('Unable to write route artifact: ' . $tmpFile);
|
||||
}
|
||||
|
||||
if (@rename($tmpFile, $file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows rename() cannot replace an existing file.
|
||||
if (is_file($file) && @unlink($file) && @rename($tmpFile, $file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@unlink($tmpFile);
|
||||
throw new \RuntimeException('Unable to publish route artifact: ' . $file);
|
||||
}
|
||||
|
||||
private function normalizeFiles(array $files): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($files as $file) {
|
||||
if (!is_string($file) || $file === '') {
|
||||
continue;
|
||||
}
|
||||
$normalized[] = str_replace('\\', '/', realpath($file) ?: $file);
|
||||
}
|
||||
|
||||
return array_values(array_unique($normalized));
|
||||
}
|
||||
|
||||
private function getFilePath(string $type): string
|
||||
|
||||
+15
-4
@@ -214,6 +214,18 @@ class Router
|
||||
|
||||
$changedFiles = $container->get(HotReloadState::class)->consume();
|
||||
|
||||
$artifactState = $container->get(RouteArtifactState::class);
|
||||
if (getenv('KIRI_FILE_BUILD') !== '1' && !empty($changedFiles) && $artifactState->coversChangedFiles(static::$type, $changedFiles)) {
|
||||
$container->get(DataGrip::class)->reset(static::$type);
|
||||
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||
if ($router->importArtifact($artifactState->load(static::$type))) {
|
||||
$this->read_dir_file(APP_PATH . 'routes');
|
||||
$this->reset($container);
|
||||
$coordinator->done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Worker 首次启动(无变更文件 + Master 已完成扫描):
|
||||
// 重新 include 路由文件(Router::get/post 显式注册) + 基于 Master 扫描清单重建注解路由
|
||||
// 避免 opcache_compile_file,仅用 Reflection 重建路由,内存开销极小
|
||||
@@ -232,7 +244,6 @@ class Router
|
||||
$container->get(DataGrip::class)->reset(static::$type);
|
||||
|
||||
$scanner = $container->get(Kiri\Di\Scanner::class);
|
||||
$artifactState = $container->get(RouteArtifactState::class);
|
||||
$scanConfig = array_merge(
|
||||
config('servers.reload.scan', []),
|
||||
config('site.scanner', [])
|
||||
@@ -241,12 +252,12 @@ class Router
|
||||
|
||||
$normalizedAppPath = str_replace('\\', '/', realpath(APP_PATH . 'app') ?: APP_PATH . 'app');
|
||||
$normalizedRoutePath = str_replace('\\', '/', realpath(APP_PATH . 'routes') ?: APP_PATH . 'routes');
|
||||
$routeChanged = false;
|
||||
$routeChangedFiles = [];
|
||||
$appChangedFiles = [];
|
||||
|
||||
foreach ($changedFiles as $changedFile) {
|
||||
if (str_starts_with($changedFile, $normalizedRoutePath . '/')) {
|
||||
$routeChanged = true;
|
||||
$routeChangedFiles[] = $changedFile;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -265,7 +276,7 @@ class Router
|
||||
if ($canUseArtifact) {
|
||||
$artifact = $artifactState->load(static::$type);
|
||||
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||
$usedArtifact = $router->importArtifact($artifact, $appChangedFiles);
|
||||
$usedArtifact = $router->importArtifact($artifact, array_merge($appChangedFiles, $routeChangedFiles));
|
||||
}
|
||||
|
||||
// routes 目录中的显式路由文件必须每次重建路由表时重新 include。
|
||||
|
||||
@@ -282,10 +282,6 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
||||
|
||||
public function importArtifact(array $artifact, array $excludeSourceFiles = []): bool
|
||||
{
|
||||
if (($artifact['has_closure_routes'] ?? false) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = $artifact['entries'] ?? null;
|
||||
if (!is_array($entries)) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user