Laravel Blaze - Blade组件编译时优化引擎
项目概述
Laravel Blaze 是一个专为 Laravel 框架设计的高性能 Blade 组件优化包。它通过创新的编译时代码折叠技术,显著提升 Blade 组件渲染性能。在基准测试中,渲染 25,000 个可折叠按钮组件时,Blaze 可将渲染时间从 750ms 降低到 45ms,性能提升约 17 倍。
Blaze 的核心原理是识别并预渲染 Blade 组件中的静态部分,从而在运行时避免不必要的解析和渲染开销。它特别适合优化那些无副作用、纯 UI 构建的组件,如按钮、卡片、徽章等基础UI组件。
功能特性
🔥 核心优化技术
- 编译时代码折叠:在 Blade 编译阶段预渲染静态组件,减少运行时开销
- 智能组件识别:自动识别可折叠的组件,确保优化安全性
- 运行时记忆化:对无法折叠的组件进行结果缓存,避免重复渲染
- Aware指令支持:完整支持 Laravel 的
@aware指令系统
⚙️ 灵活的优化策略
- 参数化控制:通过
@blaze指令参数精细控制优化行为 - 渐进式采用:可针对单个组件启用或禁用特定优化
- 向后兼容:优化失败时自动回退到正常渲染模式
🛡️ 安全与稳定
- 无副作用保证:确保可折叠组件不包含运行时依赖
- 自动验证:在折叠过程中验证占位符完整性
- 错误恢复:遇到问题自动回退到标准渲染流程
🔌 生态系统集成
- Flux组件原生支持:所有符合条件的 Flux 组件自动优化
- Laravel Blade无缝集成:作为 Blade 预编译器运行,无需修改现有代码
- 开发友好:提供调试模式和详细的折叠事件追踪
安装指南
环境要求
- PHP 8.0+
- Laravel 10.0+
- Composer
安装步骤
通过 Composer 安装 Blaze 包:
composer require livewire/blaze:^1.0@beta
配置说明
Blaze 安装后自动注册服务提供者,无需额外配置。它会自动集成到 Laravel 的 Blade 编译流程中。
注意:Blaze 目前处于早期测试阶段,API 可能会有变动。请在生产环境中充分测试。
使用说明
基础使用
要优化一个 Blade 组件,只需在组件文件顶部添加 @blaze 指令:
{{-- resources/views/components/button.blade.php --}}
@blaze
@props(['variant' => 'primary'])
<button type="button" class="btn btn-{{ $variant }}">
{{ $slot }}
</button>
高级配置
@blaze 指令支持参数化配置,允许精细控制优化行为:
{{-- 启用所有优化(默认) --}}
@blaze
{{-- 显式启用所有优化 --}}
@blaze(fold: true, memo: true, aware: true)
{{-- 禁用特定优化 --}}
@blaze(fold: false, memo: true, aware: false)
参数说明:
fold: true/false- 启用编译时代码折叠(默认:true)memo: true/false- 启用运行时记忆化(默认:true)aware: true/false- 启用@aware指令支持(默认:true)
Flux 组件集成
如果您的项目使用 Flux 组件系统,所有符合条件的 Flux 组件已经自动标记为可优化。只需安装 Blaze 包即可享受性能提升,无需额外配置。
动态内容处理
对于包含动态内容的组件,Blaze 会自动检测并应用运行时记忆化:
{{-- 包含动态内容的组件 --}}
@blaze(memo: true)
@props(['user', 'items'])
<div class="user-profile">
<h2>{{ $user->name }}</h2>
@foreach($items as $item)
<div class="item">{{ $item->name }}</div>
@endforeach
</div>
调试与监控
Blaze 提供了调试功能,帮助开发者了解优化效果:
// 启用调试模式
Blaze::debug();
// 获取折叠事件统计
$foldedEvents = Blaze::flushFoldedEvents();
核心代码解析
1. BlazeManager - 核心管理器
BlazeManager 是整个优化流程的协调中心,负责组件折叠的调度和执行:
<?php
namespace Livewire\Blaze;
use Livewire\Blaze\Events\ComponentFolded;
use Livewire\Blaze\Nodes\ComponentNode;
use Livewire\Blaze\Tokenizer\Tokenizer;
use Livewire\Blaze\Parser\Parser;
use Livewire\Blaze\Walker\Walker;
use Livewire\Blaze\Folder\Folder;
use Livewire\Blaze\Memoizer\Memoizer;
class BlazeManager
{
protected $foldedEvents = [];
protected $enabled = true;
protected $debug = false;
public function __construct(
protected Tokenizer $tokenizer,
protected Parser $parser,
protected Walker $walker,
protected Folder $folder,
protected Memoizer $memoizer,
) {
// 监听组件折叠事件
Event::listen(ComponentFolded::class, function (ComponentFolded $event) {
$this->foldedEvents[] = $event;
});
}
public function compile(string $template): string
{
// 保护原始区块
$template = (new BladeService)->preStoreVerbatimBlocks($template);
// 分词 -> 解析 -> 遍历 -> 折叠
$tokens = $this->tokenizer->tokenize($template);
$ast = $this->parser->parse($tokens);
$dataStack = [];
$ast = $this->walker->walk(
nodes: $ast,
preCallback: function ($node) use (&$dataStack) {
// 设置组件父级属性上下文
if ($node instanceof ComponentNode) {
$node->setParentsAttributes($dataStack);
}
return $node;
},
postCallback: function ($node) use (&$dataStack) {
// 应用折叠和记忆化优化
return $this->memoizer->memoize($this->folder->fold($node));
},
);
$output = $this->render($ast);
(new BladeService)->deleteTemporaryCacheDirectory();
return $output;
}
}
2. Folder - 组件折叠器
Folder 类负责实际执行组件的编译时折叠操作:
<?php
namespace Livewire\Blaze\Folder;
use Livewire\Blaze\Exceptions\LeftoverPlaceholdersException;
use Livewire\Blaze\Exceptions\InvalidBlazeFoldUsageException;
use Livewire\Blaze\Support\AttributeParser;
use Livewire\Blaze\Events\ComponentFolded;
use Livewire\Blaze\Nodes\ComponentNode;
use Livewire\Blaze\Nodes\TextNode;
use Livewire\Blaze\Directive\BlazeDirective;
class Folder
{
protected $renderBlade;
protected $renderNodes;
protected $componentNameToPath;
public function isFoldable(Node $node): bool
{
if (! $node instanceof ComponentNode) {
return false;
}
try {
$componentPath = ($this->componentNameToPath)($node->name);
if (empty($componentPath) || !file_exists($componentPath)) {
return false;
}
$source = file_get_contents($componentPath);
$directiveParameters = BlazeDirective::getParameters($source);
// 默认启用折叠功能
return $directiveParameters['fold'] ?? true;
} catch (\Exception $e) {
return false;
}
}
public function fold(Node $node): Node
{
if (! $node instanceof ComponentNode || ! $this->isFoldable($node)) {
return $node;
}
/** @var ComponentNode $component */
$component = $node;
// 获取组件路径和源代码
$componentPath = ($this->componentNameToPath)($component->name);
$source = file_get_contents($componentPath);
// 解析组件属性中的动态内容
$attributeParser = new AttributeParser();
$attributes = $component->attributes;
// 创建占位符替换动态内容
$attributePlaceholders = [];
$attributeNameToPlaceholder = [];
$staticAttributes = $attributeParser->parseAndReplaceDynamics(
$attributes,
$attributePlaceholders,
$attributeNameToPlaceholder
);
// 构建静态模板进行预渲染
$staticTemplate = "<x-{$component->name} {$staticAttributes} />";
try {
// 预渲染组件
$rendered = ($this->renderBlade)($staticTemplate);
// 验证占位符完整性
$this->validatePlaceholders($rendered, $attributePlaceholders);
// 恢复原始动态内容
foreach ($attributePlaceholders as $placeholder => $originalValue) {
$rendered = str_replace($placeholder, $originalValue, $rendered);
}
// 记录折叠事件
Event::dispatch(new ComponentFolded(
$component->name,
$componentPath,
filemtime($componentPath)
));
return new TextNode($rendered);
} catch (LeftoverPlaceholdersException $e) {
// 占位符验证失败,返回原始组件
return $component;
}
}
}
3. Memoizer - 运行时记忆化
Memoizer 类为无法折叠的组件提供运行时缓存优化:
<?php
namespace Livewire\Blaze\Memoizer;
use Livewire\Blaze\Nodes\ComponentNode;
use Livewire\Blaze\Nodes\TextNode;
use Livewire\Blaze\Directive\BlazeDirective;
class Memoizer
{
protected $componentNameToPath;
public function memoize(Node $node): Node
{
if (! $node instanceof ComponentNode ||
! $node->selfClosing ||
! $this->isMemoizable($node)) {
return $node;
}
$name = $node->name;
$attributes = $node->getAttributesAsRuntimeArrayString();
// 生成记忆化包装代码
$output = '<' . '?php $blaze_memoized_key = \Livewire\Blaze\Memoizer\Memo::key("' . $name . '", ' . $attributes . '); ?>';
$output .= '<' . '?php if (! \Livewire\Blaze\Memoizer\Memo::has($blaze_memoized_key)) : ?>';
$output .= '<' . '?php ob_start(); ?>';
$output .= $node->render();
$output .= '<' . '?php \Livewire\Blaze\Memoizer\Memo::put($blaze_memoized_key, ob_get_clean()); ?>';
$output .= '<' . '?php endif; ?>';
$output .= '<' . '?php echo \Livewire\Blaze\Memoizer\Memo::get($blaze_memoized_key); ?>';
return new TextNode($output);
}
public function isMemoizable(Node $node): bool
{
if (! $node instanceof ComponentNode) {
return false;
}
try {
$componentPath = ($this->componentNameToPath)($node->name);
if (empty($componentPath) || ! file_exists($componentPath)) {
return false;
}
$source = file_get_contents($componentPath);
$directiveParameters = BlazeDirective::getParameters($source);
// 默认启用记忆化
return $directiveParameters['memo'] ?? true;
} catch (\Exception $e) {
return false;
}
}
}
4. Tokenizer - 模板分词器
Tokenizer 类负责将 Blade 模板分解为可处理的标记:
<?php
namespace Livewire\Blaze\Tokenizer;
use Livewire\Blaze\Tokenizer\Tokens\TagSelfCloseToken;
use Livewire\Blaze\Tokenizer\Tokens\TagCloseToken;
use Livewire\Blaze\Tokenizer\Tokens\TagOpenToken;
use Livewire\Blaze\Tokenizer\Tokens\TextToken;
class Tokenizer
{
protected array $prefixes = [
'flux:' => ['namespace' => 'flux::', 'slot' => 'x-slot'],
'x:' => ['namespace' => '', 'slot' => 'x-slot'],
'x-' => ['namespace' => '', 'slot' => 'x-slot'],
];
public function tokenize(string $content): array
{
$this->resetTokenizer($content);
$this->tokens = [];
$state = State::TEXT;
// 状态机处理模板内容
while (!$this->isAtEnd()) {
$state = match($state) {
State::TEXT => $this->handleTextState(),
State::TAG_OPEN => $this->handleTagOpenState(),
State::TAG_CLOSE => $this->handleTagCloseState(),
State::ATTRIBUTE_NAME => $this->handleAttributeState(),
State::SLOT => $this->handleSlotState(),
State::SLOT_CLOSE => $this->handleSlotCloseState(),
State::SHORT_SLOT => $this->handleShortSlotState(),
default => throw new \RuntimeException("Unknown state: $state"),
};
}
$this->flushBuffer();
return $this->tokens;
}
protected function handleTextState(): State
{
// 处理文本内容,直到遇到组件标签
while (!$this->isAtEnd() && $this->currentChar() !== '<') {
$this->buffer .= $this->currentChar();
$this->advance();
}
if (!empty($this->buffer)) {
$this->tokens[] = new TextToken($this->buffer);
$this->buffer = '';
}
if ($this->isAtEnd()) {
return State::TEXT;
}
// 检查是否为组件标签
foreach ($this->prefixes as $prefix => $config) {
if ($this->peek(strlen($prefix) + 1) === "<{$prefix}") {
$this->currentPrefix = $prefix;
$this->advance(); // 跳过 <
return State::TAG_OPEN;
}
}
// 不是组件标签,继续文本处理
$this->buffer .= $this->currentChar();
$this->advance();
return State::TEXT;
}
}